216

Jackson Annotations for JSON (Part 3): Deserialization - DZone Java

 6 years ago
source link: https://dzone.com/articles/jackson-annotations-for-json-part-3-deserialization
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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.

Dec. 25, 17 · Tutorial

Jackson 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:

@JsonSetter Test Output

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\": \"[email protected]\", \"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:

@JsonAnySetter Test Output

@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.

JsonCreator.png?resize=863%2C120

@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:

@JacksonInject Test Output

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:

@JsonDeserialize Test Output

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK