Jackson custom serializers - java

Can a single custom serializer be used as both key serializer and normal object serializer?
i.e. sometime use the serializer as a key serializer for serializing map keys while at other time serializing the object normally.
I was facing issues with JsonGenerator object passed to the serializer method.
When used as a key serializer it expects a field name but when using normally, it expects the value.

You can use the same custom serializer but you need to distinguish somehow whether you need to generate property or whole object. Map is serialized to JSON Object where Map keys are converted to JSON Object properties. To generate property with Jackson we need to use writeFieldName method. To distinguish how you would like to use this serialiser in constructor you can provide this information. Example:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.Collections;
public class JsonApp {
public static void main(String[] args) throws Exception {
SimpleModule userModule = new SimpleModule();
userModule.addSerializer(User.class, new UserJsonSerializer(false));
userModule.addKeySerializer(User.class, new UserJsonSerializer(true));
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModule(userModule);
User user = new User();
System.out.println(mapper.writeValueAsString(Collections.singletonMap(user, user)));
}
}
class User {
private String firstName = "Tom";
private String lastName = "Smith";
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
class UserJsonSerializer extends JsonSerializer<User> {
private final boolean generateKey;
UserJsonSerializer(boolean generateKey) {
this.generateKey = generateKey;
}
#Override
public void serialize(User value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
if (generateKey) {
serializeAsMapKey(value, gen);
} else {
serializeAsObject(value, gen);
}
}
private void serializeAsMapKey(User value, JsonGenerator gen) throws IOException {
gen.writeFieldName(String.join(",", value.getFirstName(), value.getLastName()));
}
private void serializeAsObject(User value, JsonGenerator gen) throws IOException {
gen.writeStartObject();
gen.writeFieldName("first");
gen.writeString(value.getFirstName());
gen.writeFieldName("last");
gen.writeString(value.getLastName());
gen.writeEndObject();
}
}
Above code prints:
{
"Tom,Smith" : {
"first" : "Tom",
"last" : "Smith"
}
}
If you do not have any common logic you can just create two separate classes: UserJsonSerializer and UserKeyJsonSerializer which is an object oriented and clear solution.

Related

Jackson skip field when creating JSON [duplicate]

This question already has answers here:
What is use of the annotation #JsonIgnore?
(3 answers)
Closed 5 months ago.
I have a class like
public class MyPojo {
String name,
String age
String sub
}
And map like
map("name":"john","age":21)
Using Jacksons ObjectMapper, I get a string like
{
"name": "john",
"age": "21",
"sub": null
}
but instead I want to exclude the sub:
{
"name": "john",
"age": "21"
}
How can I do that and tell Jackson to skip sub?
P.S. Please keep in mind that I want to have ability to exclude age and include sub without changing the POJO class, so #JsonIgnore doesn't quite fit.
You can use java.util.Optional in your POJO class. You can convert Map to POJO and after that serialise it ignoring null-s. Optional allows to distinguish map.put("property", null) from not setting property at all. See below example:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class DateApp {
private final static JsonMapper JSON_MAPPER = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.addModule(new Jdk8Module())
.build();
public static void main(String[] args) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("name", "John");
map.put("age", 21);
MyPojo pojo = JSON_MAPPER.convertValue(map, MyPojo.class);
System.out.println(pojo);
System.out.println("JSON:");
JSON_MAPPER.writeValue(System.out, pojo);
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
class MyPojo {
private Optional<String> name;
private Optional<String> age;
private Optional<String> sub;
}
Above code prints:
MyPojo(name=Optional[John], age=Optional[21], sub=null)
JSON:
{
"name" : "John",
"age" : "21"
}
You can try this approach in order to avoid the null attributes in the final json.
I have used ObjectMapper object and set the below property to avoid null attributes in the json.
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Code as follows:
MyPojo.java
public class MyPojo {
private String name;
private String age;
private String sub;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getSub() {
return sub;
}
public void setSub(String sub) {
this.sub = sub;
}
#Override
public String toString() {
return "MyPojo{" +
"name=" + name +
", age=" + age +
", sub=" + sub +
'}';
}
}
Test.java
public class Test {
public static void main(String[] args) throws JsonProcessingException {
Map<String,String> inputMap = new HashMap<>();
inputMap.put("age","21");
inputMap.put("name","John");
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
MyPojo p = mapper.convertValue(inputMap,MyPojo.class);
System.out.println(p);
System.out.println(mapper.writeValueAsString(p));
}
}
Output:
MyPojo{name=John, age=21, sub=null}
{"name":"John","age":"21"}
You can create your custom serializer.
Just include your map in the serializer code
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
#JsonSerialize(using = MyPojoSerializer.class)
public class MyPojo {
String name;
String age;
String sub;
}
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class MyPojoSerializer extends JsonSerializer<MyPojo> {
#Override
public void serialize(MyPojo myPojo, JsonGenerator jGen, SerializerProvider serializerProvider) throws IOException {
jGen.writeStartObject();
// Map map = ....
for (final Field field : myPojo.getClass().getDeclaredFields()) {
ReflectionUtils.makeAccessible(field);
final String fieldName = field.getName();
final Object fieldValue = ReflectionUtils.getField(field, myPojo);
if (map.containsKey(fieldName)) {
jGen.writeFieldName(fieldName);
jGen.writeObject(fieldValue);
}
}
jGen.writeEndObject();
}
}

How to conditionally exclude property from a json response

I have a class pojo used to return a response to an API call in the rest controller
EmployeeResponse response = validationService.validate(request);
return new ResponseEntity<>(response, HttpStatus.OK);
However now we want to feature flag the controller class so that if a configuration property is not set, the response will not include a property. How can we do that?
public class EmployeeResponse {
private String firstName;
private String lastName
private String address; // don't want to include this if boolean flag is not set
}
EDIT: adding the controller code here to show that an object is returned without being serialized so I don't see how to fit objectMapper into that
#RestController
public class EmployeeController {
#PostMapping(value = "/validate", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<EmployeeResponse> get(final #RequestBody EmployeeRequest employeeRequest) {
MasterSubResponse response = validationService.validate(employeeRequest);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
You can use Jackson Filter to control the serialization process. When using JSON format, Spring Boot will use an ObjectMapper instance to serialize responses and deserialize requests. The idea is to create custom filter where you will place business logic for conditionally rendering desired field from DTO. Then you should add that filter to object mapper.
To summarize,here are the steps youn need to follow :
Anottate your DTO class with #JsonFilter("myFilter")
Create implementation class for your custom filter
Create configuration class for ObjectMapper where you will set filter created in step 1.
Create your boolean flag in application.properties file
Step 1:
import com.fasterxml.jackson.annotation.JsonFilter;
#JsonFilter("myFilter")
public class EmployeeResponse {
private String firstName;
private String lastName;
private String address;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Step 2:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
public class CustomFilter extends SimpleBeanPropertyFilter implements PropertyFilter {
private boolean isSerializable;
#Override
public void serializeAsField
(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
throws Exception {
if (include(writer)) {
if (!writer.getName().equals("address")) {
writer.serializeAsField(pojo, jgen, provider);
return;
}
System.out.println(isSerializable);
if (isSerializable) {
writer.serializeAsField(pojo, jgen, provider);
}
} else if (!jgen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
#Override
protected boolean include(BeanPropertyWriter writer) {
return true;
}
#Override
protected boolean include(PropertyWriter writer) {
return true;
}
public boolean isSerializable() {
return isSerializable;
}
public void setSerializable(boolean serializable) {
isSerializable = serializable;
}
}
Step 3:
import com.example.demo.filter.CustomFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
#Configuration
public class ObjectMapperCofiguration {
#Value("${isSerializable}")
public boolean isSerializable;
#Configuration
public class FilterConfiguration {
public FilterConfiguration(ObjectMapper objectMapper) {
SimpleFilterProvider simpleFilterProvider = new SimpleFilterProvider().setFailOnUnknownId(true);
CustomFilter customFilter = new CustomFilter();
customFilter.setSerializable(isSerializable);
simpleFilterProvider.addFilter("myFilter", customFilter);
objectMapper.setFilterProvider(simpleFilterProvider);
}
}
}
Step 4 :
In application.properties file add following property :
isSerializable= false
Step 5:
Create Controller class to test it:
#RestController
public class RestSpringBootController {
#GetMapping(path = "/test")
public ResponseEntity<EmployeeResponse> test() throws JsonProcessingException {
EmployeeResponse employeeResponse = new EmployeeResponse();
employeeResponse.setAddress("addres");
employeeResponse.setFirstName("first");
employeeResponse.setLastName("last");
ResponseEntity<EmployeeResponse> responseEntity = ResponseEntity.ok(employeeResponse);
return responseEntity;
}
}
Finally, when you start your SpringBoot app, with boolean flag isSerializable set to false you should get following response:
If you set isSerializable flag to true and restart the app, you shoud see following response:

remove an entry from HashMap depending on the Enum type of the value

I need to create a json from an Object. The Object contains a List member variable of type Name. Name is a class which contains an enum NameType. below are the class:
public final class Person { private final List<Name> names; }
public class Name {
private final String first;
private final String last;
private final String middle;
private final NameType type;
}
public enum NameType {
X,
Y,
Z
}
The json that is produced for class Person should not include Name for which NameType is Y and Z. Below is the simple way in which I try to generate the json:
Map<String, Object> personMap = mapper.readValue(person!=null ? mapper.writeValueAsString(person) : "{}", typeReference);
I need to remove the key "names" from personMap. I have searched a few ways to remove it before serialization, but that hasn't worked for me. I followed this tutorial: https://www.baeldung.com/jackson-ignore-properties-on-serialization
and tried to remove it using type and using filter, but because this is an enum so didn't work for me. So, I have 2 questions:
Is there a way to not include the Name property as part of serialization based on NameType.
If there is any way to remove after serialization is done.
Below is the structure that is generated for the map.
Thanks in Advance!!
Disclaimer: I never used Jackson before, this was my first contact. I was just looking for a little puzzle to solve while drinking my morning tea, which also enables me to learn something. So I am not sure if there are better or more elegant ways of doing this.
In order to present an MCVE which everyone can easily compile and execute, here are my more complete versions (including getters) of your example classes:
package de.scrum_master.stackoverflow.q71358052;
public enum NameType {
REAL,
ARTIST,
ONLINE
}
package de.scrum_master.stackoverflow.q71358052;
public class Name {
private final String first;
private final String last;
private final String middle;
private final NameType type;
public Name(String first, String last, String middle, NameType type) {
this.first = first;
this.last = last;
this.middle = middle;
this.type = type;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
public String getMiddle() {
return middle;
}
public NameType getType() {
return type;
}
}
package de.scrum_master.stackoverflow.q71358052;
import java.util.List;
public final class Person {
private final List<Name> names;
public Person(List<Name> names) {
this.names = names;
}
public List<Name> getNames() {
return names;
}
}
As described for a similar case in this tutorial, we can use a custom JsonSerializer<Name> in combination with a BeanSerializerModifier, registering them on the ObjectMapper in a SimpleModule:
package de.scrum_master.stackoverflow.q71358052;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class NameSerializer extends JsonSerializer<Name> {
private final JsonSerializer<Object> defaultSerializer;
public NameSerializer(JsonSerializer<Object> serializer) {
defaultSerializer = serializer;
}
#Override
public void serialize(Name value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value.getType() != NameType.REAL)
return;
defaultSerializer.serialize(value, gen, serializers);
}
#Override
public boolean isEmpty(SerializerProvider provider, Name value) {
return value == null || value.getType() != NameType.REAL;
}
}
package de.scrum_master.stackoverflow.q71358052;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import java.util.List;
public class JacksonDemo {
public static void main(String[] args) throws JsonProcessingException {
Person person = new Person(List.of(
new Name("Edward", "Robinson", "G.", NameType.ARTIST),
new Name("Emanuel", "Goldenberg", null, NameType.REAL),
new Name("Эмануэль", "Голденберг", null, NameType.REAL),
new Name("Eddie", "The Gangster", null, NameType.ONLINE)
));
ObjectMapper objectMapper = getObjectMapper();
String personJson = objectMapper.writeValueAsString(person);
System.out.println(personJson.contains("ARTIST")); // false
System.out.println(personJson.contains("ONLINE")); // false
System.out.println(personJson.contains("Goldenberg")); // true
System.out.println(personJson.contains("Голденберг")); // true
System.out.println(personJson);
}
private static ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
objectMapper.registerModule(
new SimpleModule() {
#Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(
new BeanSerializerModifier() {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (Name.class.isAssignableFrom(beanDesc.getBeanClass()))
return new NameSerializer((JsonSerializer<Object>) serializer);
return serializer;
}
}
);
}
}
);
return objectMapper;
}
}
The console log should be:
false
false
true
true
{"names":[{"first":"Emanuel","last":"Goldenberg","type":"REAL"},{"first":"Эмануэль","last":"Голденберг","type":"REAL"}]}
Oh, by the way and just in case not everybody knows who Edward G. Robinson was...

Deserialize JSON array to a single Java object with Jackson

The idea is that I'd like to convert a JSON array ["foo", "bar"] into a Java object so I need to map each array element to property by index.
Suppose I have the following JSON:
{
"persons": [
[
"John",
"Doe"
],
[
"Jane",
"Doe"
]
]
}
As you can see each person is just an array where the first name is an element with index 0 and the last name is an element with index 1.
I would like to deserialize it to List<Person>.
I use mapper as follows:
mapper.getTypeFactory().constructCollectionType(List.class, Person.class)
where Person.class is:
public class Person {
public final String firstName;
public final String lastName;
#JsonCreator
public Person(#JsonProperty() String firstName, #JsonProperty String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
I was wondering if I can somehow specify array index as #JsonProperty argument instead of it's key name?
Thanks to bureaquete for suggestion to use custom Deserializer. But it was more suitable for me to register it with SimpleModule instead of #JsonDeserialize annotation. Below is complete JUnit test example:
#RunWith(JUnit4.class)
public class MapArrayToObjectTest {
private static ObjectMapper mapper;
#BeforeClass
public static void setUp() {
mapper = new ObjectMapper();
SimpleModule customModule = new SimpleModule("ExampleModule", new Version(0, 1, 0, null));
customModule.addDeserializer(Person.class, new PersonDeserializer());
mapper.registerModule(customModule);
}
#Test
public void wrapperDeserializationTest() throws IOException {
//language=JSON
final String inputJson = "{\"persons\": [[\"John\", \"Doe\"], [\"Jane\", \"Doe\"]]}";
PersonsListWrapper deserializedList = mapper.readValue(inputJson, PersonsListWrapper.class);
assertThat(deserializedList.persons.get(0).lastName, is(equalTo("Doe")));
assertThat(deserializedList.persons.get(1).firstName, is(equalTo("Jane")));
}
#Test
public void listDeserializationTest() throws IOException {
//language=JSON
final String inputJson = "[[\"John\", \"Doe\"], [\"Jane\", \"Doe\"]]";
List<Person> deserializedList = mapper.readValue(inputJson, mapper.getTypeFactory().constructCollectionType(List.class, Person.class));
assertThat(deserializedList.get(0).lastName, is(equalTo("Doe")));
assertThat(deserializedList.get(1).firstName, is(equalTo("Jane")));
}
}
class PersonsListWrapper {
public List<Person> persons;
}
class Person {
final String firstName;
final String lastName;
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
class PersonDeserializer extends JsonDeserializer<Person> {
#Override
public Person deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = jp.readValueAsTree();
return new Person(node.get(0).getTextValue(), node.get(1).getTextValue());
}
}
Note that if you do not need wrapper object, you can deserialize JSON array
[["John", "Doe"], ["Jane", "Doe"]] directly to List<Person> using mapper as follows:
List<Person> deserializedList = mapper.readValue(inputJson, mapper.getTypeFactory().constructCollectionType(List.class, Person.class));
It is easy to serialize, but not so easy to deserialize in such manner;
The following class can be serialized into an array of strings as in your question with #JsonValue;
public class Person {
private String firstName;
private String lastName;
//getter,setter,constructors
#JsonValue
public List<String> craeteArr() {
return Arrays.asList(this.firstName, this.lastName);
}
}
But to deserialize, I had to create a wrapper class, and use custom deserialization with #JsonDeserialize;
public class PersonWrapper {
#JsonDeserialize(using = CustomDeserializer.class)
private List<Person> persons;
//getter,setter,constructors
}
and the custom deserializer itself;
public class CustomDeserializer extends JsonDeserializer<List<Person>> {
#Override
public List<Person> deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
JsonNode node = jsonParser.readValueAsTree();
ObjectMapper mapper = new ObjectMapper();
return IntStream.range(0, node.size()).boxed()
.map(i -> {
try {
List<String> values = mapper.readValue(node.get(i).toString(), List.class);
return new Person().setFirstName(values.get(0)).setLastName(values.get(1));
} catch (IOException e) {
throw new RuntimeException();
}
}).collect(Collectors.toList());
}
}
You need to put proper validation in deserializer logic to check that each mini-array contains exactly two values, but this works well.
I'd rather use these steps, and maybe to hide #JsonDeserialize, I'd do the following;
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotationsInside
#JsonDeserialize(using = CustomDeserializer.class)
public #interface AcceptPersonAsArray {}
So you can use some custom annotation in PersonWrapper
public class PersonWrapper {
#AcceptPersonAsArray
private List<Person> persons;
//getter,setter,constructors
}

Deserializing stringified (quote enclosed) nested objects with Jackson

I am consuming a "RESTful" service (via RestTemplate) that produces JSON as follows:
{
"id": "abcd1234",
"name": "test",
"connections": {
"default": "http://foo.com/api/",
"dev": "http://dev.foo.com/api/v2"
},
"settings": {
"foo": "{\n \"fooId\": 1, \"token\": \"abc\"}",
"bar": "{\"barId\": 2, \"accountId\": \"d7cj3\"}"
}
}
Note the settings.foo and settings.bar values, which cause issues on deserialization. I would like to be able to deserialize into objects (e.g., settings.getFoo().getFooId(), settings.getFoo().getToken()).
I was able to solve this specifically for an instance of Foo with a custom deserializer:
public class FooDeserializer extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
String text = node.toString();
String trimmed = text.substring(1, text.length() - 1);
trimmed = trimmed.replace("\\", "");
trimmed = trimmed.replace("\n", "");
ObjectMapper mapper = new ObjectMapper();
JsonNode obj = mapper.readTree(trimmed);
Foo result = mapper.convertValue(obj, Foo.class);
return result;
}
}
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class Settings {
#JsonDeserialize(using = FooDeserializer.class)
private Foo foo;
private Bar bar;
}
However, now if I want to deserialize settings.bar, I need to implement another custom deserializer. So I implemented a generic deserializer as follows:
public class QuotedObjectDeserializer<T> extends JsonDeserializer<T> implements ContextualDeserializer {
private Class<?> targetType;
private ObjectMapper mapper;
public QuotedObjectDeserializer(Class<?> targetType, ObjectMapper mapper) {
this.targetType = targetType;
this.mapper = mapper;
}
#Override
public JsonDeserializer<T> createContextual(DeserializationContext context, BeanProperty property) {
this.targetType = property.getType().containedType(1).getRawClass();
return new QuotedObjectDeserializer<T>(this.targetType, this.mapper);
}
#Override
public T deserialize(JsonParser jp, DeserializationContext context) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
String text = node.toString();
String trimmed = text.substring(1, text.length() - 1);
trimmed = trimmed.replace("\\", "");
trimmed = trimmed.replace("\n", "");
JsonNode obj = this.mapper.readTree(trimmed);
return this.mapper.convertValue(obj, this.mapper.getTypeFactory().constructType(this.targetType));
}
}
Now I'm not sure how to actually use the deserializer, as annotating Settings.Foo with #JsonDeserialize(using = QuotedObjectDeserializer.class) obviously does not work.
Is there a way to annotate properties to use a generic custom deserializer? Or, perhaps more likely, is there a way to configure the default deserializers to handle the stringy objects returned in my example JSON?
Edit: The problem here is specifically deserializing settings.foo and settings.bar as objects. The JSON representation has these objects wrapped in quotes (and polluted with escape sequences), so they are deserialized as Strings.
Sorry about the length of the code here. There are plenty of shortcuts here (no encapsulation; added e to defaulte to avoid keyword etc.) but the intent is there
Model class:
package com.odwyer.rian.test;
import java.io.IOException;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Model {
public String id;
public String name;
public Connections connections;
public Settings settings;
public static class Connections {
public String defaulte;
public String dev;
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
public static class Foo {
public Foo () {}
#JsonCreator
public static Foo create(String str) throws JsonParseException, JsonMappingException, IOException {
return (new ObjectMapper()).readValue(str, Foo.class);
}
public Integer fooId;
public String token;
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
public static class Bar {
public Bar() {}
#JsonCreator
public static Bar create(String str) throws JsonParseException, JsonMappingException, IOException {
return (new ObjectMapper()).readValue(str, Bar.class);
}
public Integer barId;
public String accountId;
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
public static class Settings {
public Foo foo;
public Bar bar;
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
The caller:
package com.odwyer.rian.test;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestClass {
private static ObjectMapper objectMapper = new ObjectMapper();
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
Scanner file = new Scanner(new File("test.json"));
String jsonStr = file.useDelimiter("\\Z").next();
Model model = objectMapper.readValue(jsonStr, Model.class);
System.out.println(model.toString());
}
}
The result (too much hassle to format out but it is all there!):
com.odwyer.rian.test.Model#190083e[id=abcd1234,name=test,connections=com.odwyer.rian.test.Model$Connections#170d1f3f[defaulte=http://foo.com/api/,dev=http://dev.foo.com/api/v2],settings=com.odwyer.rian.test.Model$Settings#5e7e6ceb[foo=com.odwyer.rian.test.Model$Foo#3e20e8c4[fooId=1,token=abc],bar=com.odwyer.rian.test.Model$Bar#6291bbb9[barId=2,accountId=d7cj3]]]
The key, courtesy of Ted and his post (https://stackoverflow.com/a/8369322/2960707) is the #JsonCreator annotation

Categories