Jackson Annotations for JSON (Part 3): Deserialization
As we continue our journey through Jackson annotations, let's see what you can use to help control deserialization of JSON into POJOs.
Join the DZone community and get the full member experience.
Join For FreeJackson is a suite of data-processing tools for Java comprising of three components:
Streaming (jackson-core) defines low-level streaming APIs and includes JSON-specific implementations.
Annotations (jackson-annotations) contains standard Jackson annotations.
Databind (jackson-databind) implements data-binding (and object serialization) support on the streaming package. This package depends on both the streaming and annotations packages.
In this series of articles, I will explain data binding Java objects to JSON using Jackson annotations. I will take up each of the Jackson annotations and explain, with code snippets, how to use them. Each annotation usage is accompanied with proper test cases.
If you want to catch up on what's happened so far, read:
Deserialization Annotations
Let us explore the JSON annotations that can be used to control deserialization of JSON into POJOs. The Jackson deserialization annotations are:
- @JsonSetter
- @JsonAnySetter
- @JsonCreator
- @JacksonInject
- @JsonDeserialize
@JsonSetter
The @JsonSetter annotation tells Jackson to deserialize the JSON into Java object using the name given in the setter method. Use this annotation when your JSON property names are different to the fields of the Java object class, and you want to map them.
A Java class that uses the @JsonSetter annotation is:
SetterDemoBean.java
package guru.springframework.blog.jsonannotation.domain.deserialization;
import com.fasterxml.jackson.annotation.JsonSetter;
public class SetterDemoBean {
public long personId = 0;
public String name = "James Clark";
@JsonSetter("id")
public void setPersonId(long personId) {
this.personId = personId;
}
@Override
public String toString() {
return "SetterDemoBean{" +
"personId=" + personId +
", name='" + name + '\'' +
'}';
}
}
The @JsonSetter annotation takes the name of the JSON key that must be mapped to the setter method.
The test code to test the @JsonSetter annotation is:
@Test
public void testDeSerializingWithJsonSetter() throws IOException {
String jsonString = "{\"id\": 231, \"name\": \"Mary Parker\"}";
ObjectMapper mapper = new ObjectMapper();
SetterDemoBean bean = objectMapper.readValue(jsonString, SetterDemoBean.class);
System.out.println(bean);
assertThat(bean.name, is(equalTo("Mary Parker")));
assertThat(bean.personId, is(equalTo(231L)));
}
The output of running the test in IntelliJ is:
As you can see, the JSON to be serialized has a property id. But no field in the POJO matches this property. Now how will Jackson read this JSON? Here is where the @JsonSetter annotation can be used to map the property id to the field personId. This annotation instructs Jackson to use a setter method for a given JSON property.
@JsonAnySetter
The @JsonAnySetter annotation is used on setter methods of a Map field. Sometimes you may find some JSON values that cannot be mapped to the fields in the Java object class. In such a case, the @JsonAnySetter captures the data and stores them in a Map.
A Java class that uses the @JsonAnySetter annotation is:
AnySetterDemoBean.java
package guru.springframework.blog.jsonannotation.domain.deserialization;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import java.util.HashMap;
import java.util.Map;
public class AnySetterDemoBean {
public long personId = 123L;
public String personName = "James Clark";
private Map<String, String> properties = new HashMap<String, String>();
@JsonAnySetter
public void setProperties(String key, String value){
properties.put(key, value);
}
@Override
public String toString() {
return "AnySetterDemoBean{" +
"personId=" + personId +
", personName='" + personName + '\'' +
", properties=" + properties +
'}';
}
}
The test code to test the @JsonAnySetter annotation is:
@Test
public void testDeSerializingWithJsonSetter() throws IOException {
String jsonString = "{\"personId\": 231, \"personName\": \"Mary Parker\", \"emailId\": \"mary@gmail.com\", \"gender\": \"female\"}";
ObjectMapper mapper = new ObjectMapper();
AnySetterDemoBean bean = objectMapper.readValue(jsonString, AnySetterDemoBean.class);
System.out.println(bean);
assertThat(bean.personName, is(equalTo("Mary Parker")));
assertThat(bean.personId, is(equalTo(231L)));
assertEquals("female", bean.getProperties().get("gender"));
}
The output of running the test in IntelliJ is:
@JsonCreator
The @JsonCreator annotation tells Jackson that the JSON properties can be mapped to the fields of a constructor of the POJO. This is helpful when the JSON properties do not match with the names of the Java object field names. The @JsonCreator annotation can be used where @JsonSetter cannot be used. For example, immutable objects which need their initial values to be injected through constructors.
An example of Java class that uses the @JsonCreator annotation is:
CreatorDemoBean.java
package guru.springframework.blog.jsonannotation.domain.deserialization;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class CreatorDemoBean {
public long personId = 0;
public String name = "James Clark";
@JsonCreator
public CreatorDemoBean(@JsonProperty("id") long personId, @JsonProperty("name") String name) {
this.personId = personId;
this.name = name;
}
@Override
public String toString() {
return "CreatorDemoBean{" +
"personId=" + personId +
", name='" + name + '\'' +
'}';
}
}
The test code to test the @JsonCreator annotation is:
@Test
public void testDeSerializingWithJsonCreator() throws IOException {
String jsonString = "{\"id\": 231, \"name\": \"Mary Parker\"}";
ObjectMapper mapper = new ObjectMapper();
CreatorDemoBean bean = objectMapper.readValue(jsonString, CreatorDemoBean.class);
System.out.println(bean);
assertThat(bean.name, is(equalTo("Mary Parker")));
assertThat(bean.personId, is(equalTo(231L)));
}
The output of running the test in IntelliJ is this.
@JacksonInject
The @JacksonInject annotation is used to tell Jackson that particular values of the deserialized object will be injected and not read from the JSON string.
An example of Java class where the personId field is injected by Jackson is:
JacksonInjectDemoBean.java
package guru.springframework.blog.jsonannotation.domain.deserialization;
import com.fasterxml.jackson.annotation.JacksonInject;
public class JacksonInjectDemoBean {
@JacksonInject
public long personId = 0;
public String name = "James Clark";
@Override
public String toString() {
return "JacksonInjectDemoBean{" +
"personId=" + personId +
", name='" + name + '\'' +
'}';
}
}
In order to inject values into a field, you can use the InjectableValues class. You need to configure ObjectMapper to read both, the injected values from injectableValues and the remaining values from the JSON string.
The test code to test the @JacksonInject annotation is:
@Test
public void testDeSerializingWithJacksonInject() throws IOException {
String jsonString = "{\"name\": \"Mary Parker\"}";
InjectableValues injectableValues = new InjectableValues.Std()
.addValue(long.class, 231L);
JacksonInjectDemoBean bean = new ObjectMapper().reader(injectableValues)
.forType(JacksonInjectDemoBean.class).readValue(jsonString);
System.out.println(bean);
assertThat(bean.name, is(equalTo("Mary Parker")));
assertThat(bean.personId, is(equalTo(231L)));
}
The output of running the test in IntelliJ is:
As you can see, the value for the field personId has been injected by Jackson and the other values are taken from the input JSON string.
@JsonDeserialize
The @JsonDeserialize annotation tells Jackson to use a custom deserializer while deserializing the JSON to Java object. To do so, you need to annotate the field to which you need to apply the custom deserializer.
A Java class that uses the @JsonDeserialize annotation is:
DeserializeDemoBean.java
package guru.springframework.blog.jsonannotation.domain.deserialization;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import guru.springframework.blog.jsonannotation.domain.custom.CustomDateDeserializer;
import java.util.Date;
public class DeserializeDemoBean {
public long personId = 123L;
public String name = "James Clark";
@JsonDeserialize(using = CustomDateDeserializer.class)
public Date activeDate;
@Override
public String toString() {
return "DeserializeDemoBean{" +
"personId=" + personId +
", name='" + name + '\'' +
", activeDate=" + activeDate +
'}';
}
}
The custom deserializer that is referenced by the preceding DeserializeDemoBeanbean class is:
CustomDateDeserializer.java
package guru.springframework.blog.jsonannotation.domain.custom;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.text.ParseException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CustomDateDeserializer extends StdDeserializer<Date> {
private static SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
public CustomDateDeserializer(){
this(null);
}
public CustomDateDeserializer(Class<?> c){
super(c);
}
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext
deserializationContext) throws IOException, JsonProcessingException {
String date = jsonParser.getText();
try {
return simpleDateFormat.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
Here, the CustomDateDeserializer class extends the StdDeserializer class with a generic type Date. The overriden deserialize() method returns the Date object.
The test code to test the @JsonDeserialize annotation is:
@Test
public void testDeSerializingWithJsonDeserialize() throws IOException {
String jsonString = "{\"personId\": 231, \"name\": \"Mary Parker\", " +
"\"activeDate\":\"26-09-2017 11:00:00\"}";
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
ObjectMapper mapper = new ObjectMapper();
DeserializeDemoBean bean = objectMapper.readValue(jsonString, DeserializeDemoBean.class);
System.out.println(bean);
assertThat(bean.name, is(equalTo("Mary Parker")));
assertThat(bean.personId, is(equalTo(231L)));
assertEquals("26-09-2017 11:00:00", simpleDateFormat.format(bean.activeDate));
}
The output of running the test in IntelliJ is:
Published at DZone with permission of John Thompson, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments