Saturday, April 20, 2019

Jackson Annotations - For creating Spring Boot JPA Restful Webserives




Most of the time, while working with Jackson within a Spring Boot project, I encountered an issue Which I would like to share with you all.
What is Jackson?
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.
Jackson is currently the leading option for parsing JSON in Java. The Jackson library is composed of three components: Jackson Databind, Core, and Annotation. Jackson Databind has internal dependencies on Jackson Core and Annotation. Therefore, adding Jackson Databind to your Maven POM dependency list will include the other dependencies as well. To use the latest Jackson library, you need to add the following dependency in the Maven POM.The below dependency works well in other Spring projects, Just cross verify the version of spring boot , accordingly jackson-databind version may change.
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

Reading JSON – Data Binding in Jackson

Data binding is a JSON processing model that allows for seamless conversion between JSON data and Java objects. With data binding, you create POJOs following JavaBeans convention with properties corresponding to the JSON data. The Jackson ObjectMapper is responsible for mapping the JSON data to the POJOs.
The Jackson JSON toolkit contains a set of Java annotations which you can use to influence how JSON is read into objects, or what JSON is generated from the objects. This Jackson annotation tutorial explains how to use Jackson's annotations. 



The Jackson JSON toolkit contains a set of Java annotations which you can use to influence how JSON is read into objects, or what JSON is generated from the objects. This Jackson annotation tutorial explains how to use Jackson's annotations.



Read + Write Annotations

Jackson contains a set of annotations that affect both the reading of Java objects from JSON, as well as the writing of Java objects into JSON. I refer to these annotations as "read + write annotations". The following sections explain Jackson's read + write annotations in more detail.

@JsonIgnore

The Jackson annotation @JsonIgnore is used to tell Jackson to ignore a certain property (field) of a Java object. The property is ignored both when reading JSON into Java objects, and when writing Java objects into JSON. Here is an example class that uses the @JsonIgnore annotation:

import com.fasterxml.jackson.annotation.JsonIgnore;



public class PersonIgnore {



    @JsonIgnore

    public long    personId = 0;



    public String  name = null;

}

In the above class the property personId will not be read from JSON or written to JSON.

@JsonIgnoreProperties

The @JsonIgnoreProperties Jackson annotation is used to specify a list of properties of a class to ignore. The @JsonIgnoreProperties annotation is placed above the class declaration instead of above the individual properties (fields) to ignore. Here is an example showing how to use the @JsonIgnoreProperties annotation:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;



@JsonIgnoreProperties({"firstName", "lastName"})

public class PersonIgnoreProperties {



    public long    personId = 0;



    public String  firstName = null;

    public String  lastName  = null;



}

In this example both the properties firstName and lastName will be ignored because their names are listed inside the @JsonIgnoreProperties annotation declaration above the class declaration.

@JsonIgnoreType

The @JsonIgnoreType Jackson annotation is used to mark a whole type (class) to be ignored everywhere that type is used. Here is an example that shows you how you could use the @JsonIgnoreType annotation:

import com.fasterxml.jackson.annotation.JsonIgnoreType;



public class PersonIgnoreType {



    @JsonIgnoreType

    public static class Address {

        public String streetName  = null;

        public String houseNumber = null;

        public String zipCode     = null;

        public String city        = null;

        public String country     = null;

    }



    public long    personId = 0;



    public String  name = null;



    public Address address = null;

}

In the above example all Address instances will be ignored.

@JsonAutoDetect

The Jackson annotation @JsonAutoDetect is used to tell Jackson to include properties which are not public, both when reading and writing objects. Here is an example class showing you how you can use the @JsonAutoDetect annotation:

import com.fasterxml.jackson.annotation.JsonAutoDetect;



@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY )

public class PersonAutoDetect {



    private long  personId = 123;

    public String name     = null;



}

The JsonAutoDetect.Visibility class contains constants matching the visibility levels in Java, meaning ANY, DEFAULT, NON_PRIVATE, NONE, PROTECTED_AND_PRIVATE and PUBLIC_ONLY.

Read Annotations

Jackson contains a set of annotations that only affect how Jackson parses JSON into objects - meaning they affect Jackson's reading of JSON. I refer to these as "read annotations". The following sections explains Jackson's read annotations.

@JsonSetter

The Jackson annotation @JsonSetter is used to tell Jackson that is should match the name of this setter method to a property name in the JSON data, when reading JSON into objects. This is useful if the property names used internally in your Java class is not the same as the property name used in the JSON file.

The following Person class uses the name personId for its id property:

public class Person {



    private long   personId = 0;

    private String name     = null;



    public long getPersonId() { return this.personId; }

    public void setPersonId(long personId) { this.personId = personId; }



    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

}

But in this JSON object the name id is used instead of personId:

{

  "id"   : 1234,

  "name" : "John"

}

Without some help Jackson cannot map the id property from the JSON object to the personId field of the Java class.

The @JsonSetter annotation instructs Jackson to use a setter method for a given JSON field. In our case we add the @JsonSetter annotation above the setPersonId() method. Here is how adding the @JsonSetter annotation looks like:

public class Person {



    private long   personId = 0;

    private String name     = null;



    public long getPersonId() { return this.personId; }

    @JsonSetter("id")

    public void setPersonId(long personId) { this.personId = personId; }



    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

}

The value specified inside the @JsonSetter annotation is the name of the JSON field to match to this setter method. In this case the name is id since that is the name of the field in the JSON object we want to map to the setPersonId() setter method.

@JsonAnySetter

The Jackson annotation @JsonAnySetter instructs Jackson to call the same setter method for all unrecognized fields in the JSON object. By "unrecognized" I mean all fields that are not already mapped to a property or setter method in the Java object. Look at this Bag class:

public class Bag {



    private Map<String, Object> properties = new HashMap<>();



    public void set(String fieldName, Object value){

        this.properties.put(fieldName, value);

    }



    public Object get(String fieldName){

        return this.properties.get(fieldName);

    }

}

And then look at this JSON object:

{

  "id"   : 1234,

  "name" : "John"

}

Jackson cannot directly map the id and name property of this JSON object to the Bag class, because the Bag class contains no public fields or setter methods.

You can tell Jackson to call the set() method for all unrecognized fields by adding the @JsonAnySetter annotation, like this:

public class Bag {



    private Map<String, Object> properties = new HashMap<>();



    @JsonAnySetter

    public void set(String fieldName, Object value){

        this.properties.put(fieldName, value);

    }



    public Object get(String fieldName){

        return this.properties.get(fieldName);

    }

}

Now Jackson will call the set() method with the name and value of all unrecognized fields in the JSON object.

Keep in mind that this only has effect on unrecognized fields. If, for instance, you added a public name property or setName(String) method to the Bag Java class, then the name field in the JSON object would be mapped to that property / setter instead.

@JsonCreator

The Jackson annotation @JsonCreator is used to tell Jackson that the Java object has a constructor (a "creator") which can match the fields of a JSON object to the fields of the Java object.

The @JsonCreator annotation is useful in situations where the @JsonSetter annotation cannot be used. For instance, immutable objects do not have any setter methods, so they need their initial values injected into the constructor. Look at this PersonImmutable class as example:

public class PersonImmutable {



    private long   id   = 0;

    private String name = null;



    public PersonImmutable(long id, String name) {

        this.id = id;

        this.name = name;

    }



    public long getId() {

        return id;

    }



    public String getName() {

        return name;

    }



}

To tell Jackson that it should call the constructor of PersonImmutable we must add the @JsonCreator annotation to the constructor. But that alone is not enough. We must also annotate the parameters of the constructor to tell Jackson which fields from the JSON object to pass to which constructor parameters. Here is how the PersonImmutable class looks with the @JsonCreator and @JsonProperty annotations added:

public class PersonImmutable {



    private long   id   = 0;

    private String name = null;



    @JsonCreator

    public PersonImmutable(

            @JsonProperty("id")  long id,

            @JsonProperty("name") String name  ) {



        this.id = id;

        this.name = name;

    }



    public long getId() {

        return id;

    }



    public String getName() {

        return name;

    }



}

Notice the annotation above the constructor and the annotations before the constructor parameters. Now Jackson is capable of creating a PersonImmutable from this JSON object:

{

  "id"   : 1234,

  "name" : "John"

}

@JacksonInject

The Jackson annotation @JacksonInject is used to inject values into the parsed objects, instead of reading those values from the JSON. For instance, imagine you are downloading person JSON objects from various different sources, and would like to know what source a given person object came from. The sources may not themselves contain that information, but you can have Jackson inject that into the Java objects created from the JSON objects.

To mark a field in a Java class as a field that needs to have its value injected by Jackson, add the @JacksonInject annotation above the field. Here is an example PersonInject class with the @JacksonInject annotation added above the source field:

public class PersonInject {



    public long   id   = 0;

    public String name = null;



    @JacksonInject

    public String source = null;



}

In order to have Jackson inject values into the source field you need to do a little extra when creating the Jackson ObjectMapper. Here is what it takes to have Jackson inject values into the Java objects:

InjectableValues inject = new InjectableValues.Std().addValue(String.class, "jenkov.com");

PersonInject personInject = new ObjectMapper().reader(inject)

                        .forType(PersonInject.class)

                        .readValue(new File("data/person.json"));

Notice how the value to inject into the source attribute is set in the InjectableValues addValue() method. Notice also that the value is only tied to the type String - not to any specific field name. It is the @JacksonInject annotation that specifies what field the value is to be injected into.

If you were to download person JSON objects from multiple sources and have a different source value injected for each source, you would have to repeat the above code for each source.

@JsonDeserialize

The Jackson annotation @JsonDeserialize is used to specify a custom de-serializer class for a given field in a Java object. For instance, imagine you wanted to optimize the on-the-wire formatting of the boolean values false and true to 0 and 1.

First you would need to add the @JsonDeserialize annotation to the field you want to use the custom deserializer for. Here is how adding the @JsonDeserialize annotation to a field looks like:

public class PersonDeserialize {



    public long    id      = 0;

    public String  name    = null;



    @JsonDeserialize(using = OptimizedBooleanDeserializer.class)

    public boolean enabled = false;

}

Second, here is what the OptimizedBooleanDeserializer class referenced from the @JsonDeserialize annotation looks like:

public class OptimizedBooleanDeserializer

    extends JsonDeserializer<Boolean> {



    @Override

    public Boolean deserialize(JsonParser jsonParser,

            DeserializationContext deserializationContext) throws

        IOException, JsonProcessingException {



        String text = jsonParser.getText();

        if("0".equals(text)) return false;

        return true;

    }

}

Notice that the OptimizedBooleanDeserializer class extends JsonDeserializer with the generic type Boolean . Doing so makes the deserialize() method return a Boolean object. If you were to deserialize another type (e.g a java.util.Date) you would have to specify that type inside the generics brackets 

You obtain the value of the field to deserialize by calling the getText() method of the jsonParser parameter. You can then deserialize that text into whatever value and type your deserializer is targeted at (a Boolean in this example).

Finally you need to see what it looks like to deserialize an object with a custom deserializer and the @JsonDeserializer annotation:

PersonDeserialize person = objectMapper

        .reader(PersonDeserialize.class)

        .readValue(new File("data/person-optimized-boolean.json"));

Notice how we first need to create a reader for the PersonDeserialize class using the ObjectMapper's reader() method, and then we call readValue() on the object returned by that method.

Write Annotations

Jackson also contains a set of annotations that can influence how Jackson serializes (writes) Java objects to JSON. Each of these write (serialization) annotations will be covered in the following sections.

@JsonInclude

The Jackson annotation @JsonInclude tells Jackson only to include properties under certain circumstances. For instance, that properties should only be included if they are non-null, non-empty, or have non-default values. Here is an example that shows how you can use the @JsonInclude annotation:

import com.fasterxml.jackson.annotation.JsonInclude;



@JsonInclude(JsonInclude.Include.NON_EMPTY)

public class PersonInclude {



    public long  personId = 0;

    public String name     = null;



}

This example will only include the name property if the value set for it is non-empty, meaning is not null and is not an empty string.

A more saying name for the @JsonInclude annotation could have been @JsonIncludeOnlyWhen, but that would have been longer to write.

@JsonGetter

The @JsonGetter Jackson annotation is used to tell Jackson that a certain field value should be obtained from calling a getter method instead of via direct field access. The @JsonGetter annotation is useful if your Java class uses jQuery style for getter and setter names. For instance, instead of getPersonId() and setPersonId() you might have the methods personId() and personId(long id).

Here is an example class named PersonGetter which shows the use of the @JsonGetter annotation:

public class PersonGetter {



    private long  personId = 0;



    @JsonGetter("id")

    public long personId() { return this.personId; }



    @JsonSetter("id")

    public void personId(long personId) { this.personId = personId; }



}

As you can see, the personId() method is annotated with the @JsonGetter annotation. The value set on the @JsonGetter annotation is the name that should be used in the JSON object. Thus, the name used for the personId in the JSON object is id. The resulting JSON object would look like this:

{"id":0}

Notice also that the personId(long personId) method is annotated with the @JsonSetter annotation to make Jackson recognized that as the setter matching the id attribute in the JSON object. The @JsonSetter annotation is used when reading from JSON into Java objects - not when writing Java objects to JSON. The @JsonSetter annotation is just included for the sake of completeness.

@JsonAnyGetter

The @JsonAnyGetter Jackson annotation enables you to use a Map as container for properties that you want to serialize to JSON. Here is an example of using the @JsonAnyGetter annotation in a Java class:

public class PersonAnyGetter {



    private Map<String, Object> properties = new HashMap<>();



    @JsonAnyGetter

    public Map<String, Object> properties() {

        return properties;

    }

}

When seeing the @JsonAnyGetter annotation Jackson will obtain the Map returned from the method which @JsonAnyGetter annotates, and will treat each key-value pair in that Map as a property. In other words, all key-value pairs in the Map will be serialized to JSON as part of the PersonAnyGetter object.

@JsonPropertyOrder

The @JsonPropertyOrder Jackson annotation can be used to specify in what order the fields of your Java object should be serialized into JSON. Here is an example showing how to use the @JsonPropertyOrder annotation:

@JsonPropertyOrder({"name", "personId"})

public class PersonPropertyOrder {



    public long  personId  = 0;

    public String name     = null;



}

Normally Jackson would have serialized the properties in PersonPropertyOrder in the sequence they are found in the class. However, the @JsonPropertyOrder annotation specifies a different order where the name property will be appear first and the personId property second in the serialized JSON output.

@JsonRawValue

The @JsonRawValue Jackson annotation tells Jackson that this property value should written directly as it is to the JSON output. If the property is a String Jackson would normally have enclosed the value in quotation marks, but if annotated with the @JsonRawValue property Jackson won't do that.

To make it more clear what @JsonRawValue does, look at this class without the @JsonRawValue in use:

public class PersonRawValue {



    public long   personId = 0;



    public String address  = "$#";

}

Jackson would serialize this into this JSON string:

{"personId":0,"address":"$#"}

Now we add the @JsonRawValue to the address property, like this:

public class PersonRawValue {



    public long   personId = 0;



    @JsonRawValue

    public String address  = "$#";

}

Jackson will now omit the quotation marks when serializing the address property. The serialized JSON will thus look like this:

{"personId":0,"address":$#}

This is of course invalid JSON, so why would you want that?

Well, if the address property contained a JSON string then that JSON string would be serialized into the final JSON object as part of the JSON object structure, and not just into a string in the address field in the JSON object. To see how that would work, let us change the value of the address property like this:

public class PersonRawValue {



    public long   personId = 0;



    @JsonRawValue

    public String address  =

            "{ \"street\" : \"Wall Street\", \"no\":1}";



}

Jackson would serialize this into this JSON:

{"personId":0,"address":{ "street" : "Wall Street", "no":1}}

Notice how the JSON string is now part of the serialized JSON structure.

Without the @JsonRawValue annotation Jackson would have serialized the object to this JSON:

{"personId":0,"address":"{ \"street\" : \"Wall Street\", \"no\":1}"}

Notice how the value of the address property is now enclosed in quotation marks, and all the quotation marks inside the value are escaped.

@JsonValue

The Jackson annotation @JsonValue tells Jackson that Jackson should not attempt to serialize the object itself, but rather call a method on the object which serializes the object to a JSON string. Note that Jackson will escape any quotation marks inside the String returned by the custom serialization, so you cannot return e.g. a full JSON object. For that you should use @JsonRawValue instead (see previous section).

The @JsonValue annotation is added to the method that Jackson is to call to serialize the object into a JSON string. Here is an example showing how to use the @JsonValue annotation:

public class PersonValue {



    public long   personId = 0;

    public String name = null;



    @JsonValue

    public String toJson(){

        return this.personId + "," + this.name;

    }



}

The output you would get from asking Jackson to serialize a PersonValue object is this:

"0,null"

The quotation marks are added by Jackson. Remember, any quotation marks in the value string returned by the object are escaped.

@JsonSerialize

The @JsonSerialize Jackson annotation is used to specify a custom serializer for a field in a Java object. Here is an example Java class that uses the @JsonSerialize annotation:

public class PersonSerializer {



    public long   personId = 0;

    public String name     = "John";



    @JsonSerialize(using = OptimizedBooleanSerializer.class)

    public boolean enabled = false;

}

Notice the @JsonSerialize annotation above the enabled field.

The OptimizedBooleanSerializer will serialize a true value to 1 and a false value 0. Here is the code:

public class OptimizedBooleanSerializer extends JsonSerializer<Boolean> {



    @Override

    public void serialize(Boolean aBoolean, JsonGenerator jsonGenerator,

        SerializerProvider serializerProvider)

    throws IOException, JsonProcessingException {



        if(aBoolean){

            jsonGenerator.writeNumber(1);

        } else {

            jsonGenerator.writeNumber(0);

        }

    }

}

General Annotations

The general annotations are:
  • @JsonProperty
  • @JsonFormat
  • @JsonUnwrapped
  • @JsonView
  • @JsonManagedReference and @JsonBackReference
  • @JsonIdentityInfo
  • @JsonFilter

@JsonProperty

The @JsonProperty annotation is used to map property names with JSON keys during serialization and deserialization. By default, if you try to serialize a POJO, the generated JSON will have keys mapped to the fields of the POJO. If you want to override this behavior, you can use the @JsonProperty annotation on the fields. It takes a String attribute that specifies the name that should be mapped to the field during serialization.
You can also use this annotation during deserialization when the property names of the JSON and the field names of the Java object do not match.
Let us consider an example Java class that uses the @JsonProperty annotation.

PropertyDemoBean.java

package guru.springframework.blog.jsonannotation.domain.general;
import com.fasterxml.jackson.annotation.JsonProperty;
public class PropertyDemoBean {
    @JsonProperty("person-id")
    public long personId = 123L;
    @JsonProperty("name")
    public String  name = "James Clark";
    @Override
    public String toString() {
        return "PropertyDemoBean{" +
                "personId=" + personId +
                ", name='" + name + '\'' +
                '}';
    }
}
The test code to test the @JsonProperty annotation is:
@Test
public void testSerializingWithJsonProperty()
throws JsonProcessingException {
    String jsonString = objectMapper.writeValueAsString(new PropertyDemoBean());
    System.out.println(jsonString);
    assertThat(jsonString, containsString("James Clark"));
    assertThat(jsonString, containsString("123"));
}
@Test
public void testDeSerializingWithJsonProperty() throws IOException {
    String jsonString = "{\"person-id\": 231, \"name\": \"Mary Parker\"}";
    ObjectMapper mapper = new ObjectMapper();
    PropertyDemoBean bean = objectMapper.readValue(jsonString, PropertyDemoBean.class);
    System.out.println(bean);
    assertThat(bean.name, is(equalTo("Mary Parker")));
    assertThat(bean.personId, is(equalTo(231 L)));
}

The output of running the test in IntelliJ is:
@JsonProperty Test Output

@JsonFormat

The @JsonFormat annotation is used to tell Jackson that the format in which the value for a field is serialized. It specifies the format using the JsonFormat.Shape enum.
Let us consider an example Java class that uses the @JsonFormat annotation to modify the Date and Time format of an activeDate field.

FormatDemoBean.java

package guru.springframework.blog.jsonannotation.domain.general;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Date;
public class FormatDemoBean {
    @JsonProperty("person-id")
    public long personId = 123L;
    @JsonProperty("name")
    public String  name = "James Clark";
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
    @JsonProperty("active-date")
    public Date activeDate;
    public FormatDemoBean() {
    }
    public void setActiveDate(Date activeDate) {
        this.activeDate = activeDate;
    }
}

The test code to test the @JsonFormat annotation is:
@Test
public void testSerializingWithJsonFormat()
    throws JsonProcessingException, ParseException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        String dateAndTime = "26-09-2017 11:00:00";
        Date date = simpleDateFormat.parse(dateAndTime);
        FormatDemoBean fb = new FormatDemoBean();
        fb.setActiveDate(date);
        String jsonString = objectMapper.writeValueAsString(fb);
        System.out.println(jsonString);
        assertThat(jsonString, containsString("James Clark"));
        assertThat(jsonString, containsString("123"));
        assertThat(jsonString, containsString("26-09-2017 11:00:00"));
}

The output of running the test in IntelliJ is:
@JsonFormat Test Output

@JsonUnwrapped

The @JsonUnwrapped annotation unwraps the values during serialization and deserialization. It helps in rendering the values of a composed class as if they belonged to the parent class. Let us consider an example of Java class that uses the @JsonUnwrapped annotation.

UnwrappedDemoBean.java

package guru.springframework.blog.jsonannotation.domain.general;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
public class UnwrappedDemoBean {
    public static class Address {
        public String doorNumber = "12";
        public String streetName = "phase-1";
        public String pinCode = "123456";
        public String city = "New York";
        @Override
        public String toString() {
            return "Address{" +
                    "doorNumber='" + doorNumber + '\'' +
                    ", streetName='" + streetName + '\'' +
                    ", pinCode='" + pinCode + '\'' +
                    ", city='" + city + '\'' +
                    '}';
        }
    }
    public long personId = 0;
    public String  name = "James Clark";
    @JsonUnwrapped
    public Address address = new Address();
}

In this example, the Address class is inside the UnwrappedDemoBean class. Without the @JsonUnwrapped annotation, the serialized Java object would be similar to this:
{
    "personId": 0,
    "name": "James Clark",
    "address": {
        "doorNumber": "12",
        "streetName": "phase-1",
        "pinCode": "123456",
        "city": "New York"
    }
}

Let us see what happens when you use the @JsonUnwrapped annotation.
The test code to test the @JsonUnwrapped annotation is:
@Test
public void testSerializingWithJsonUnwrapped()
    throws JsonProcessingException {
        String jsonString = objectMapper.writeValueAsString(new UnwrappedDemoBean());
        System.out.println(jsonString);
        assertThat(jsonString, containsString("James Clark"));
        assertThat(jsonString, not(containsString("address")));
}

The output of running the test in IntelliJ is:
@JsonUnwrapped Test Output
As you can see, the Address object is unwrapped and is displayed as the properties of the parent classUnwrappedDemoBean.

@JsonView

The @JsonView annotation is used to include or exclude a property dynamically during serialization and deserialization. It tells the view in which the properties are rendered. Let us consider an example Java class that uses the @JsonView annotation with Public and Internal views.

ViewDemoBean.java

package guru.springframework.blog.jsonannotation.domain.general;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
public class ViewDemoBean {
    @JsonView(Views.Public.class)
    @JsonProperty
    public long personId = 0;
    @JsonView(Views.Public.class)
    @JsonProperty
    public String  name = "James Clark";
    @JsonView(Views.Internal.class)
    @JsonProperty
    public String gender = "male";
    @Override
    public String toString() {
        return "ViewDemoBean{" +
                "personId=" + personId +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }
}

The test code to test the @JsonView annotation is:
@Test
public void testSerializingWithJsonView()
    throws JsonProcessingException {
        String jsonString = objectMapper.writerWithView(Views.Public.class)
            .writeValueAsString(new ViewDemoBean());
        String jsonStringInternal = objectMapper.writerWithView(Views.Internal.class)
            .writeValueAsString(new ViewDemoBean());
        System.out.println(jsonString);
        System.out.println(jsonStringInternal);
        assertThat(jsonString, containsString("James Clark"));
        assertThat(jsonString, not(containsString("gender")));
        assertThat(jsonStringInternal, containsString("gender"));
}

As you can see in the test code, you need to configure the ObjectMapper to include which type of view must be used for writing the JSON from the Java object using the writerWithView() method.
The output of running the test in IntelliJ is:

When the JSON is generated in the public view, only personId and name fields are serialized omitting the gender field. But when the JSON is generated in the internal view all the fields are serialized.

@JsonManagedReference and @JsonBackReference

The @JsonManagedReference and @JsonBackReference annotation are used to create JSON structures that have a bidirectional relationship. Without this annotation, you get an error like this:
"com.fasterxml.jackson.databind.JsonMappingException:Infinite recursion (StackOverflowError)"

Let us consider an example Java class that uses the @JsonManagedReference and @JsonBackReference annotations:

ManagedReferenceDemoBean.java

package guru.springframework.blog.jsonannotation.domain.general;
import com.fasterxml.jackson.annotation.JsonManagedReference;
public class ManagedReferenceDemoBean {
    public long personId = 0;
    public String  name = "James Clark";
    @JsonManagedReference
    public BackReferenceDemoBean manager;
    public ManagedReferenceDemoBean(long personId, String name, BackReferenceDemoBean manager) {
        this.personId = personId;
        this.name = name;
        this.manager = manager;
    }
}

BackReferenceDemoBean.java

package guru.springframework.blog.jsonannotation.domain.general;
import com.fasterxml.jackson.annotation.JsonBackReference;
import java.util.ArrayList;
import java.util.List;
public class BackReferenceDemoBean {
    public long personId = 123;
    public String  name = "John Thomas";
    @JsonBackReference
    public List<ManagedReferenceDemoBean> employees;
    public BackReferenceDemoBean(long personId, String name) {
        this.personId = personId;
        this.name = name;
        employees = new ArrayList<ManagedReferenceDemoBean>();
    }
    public void addEmployees(ManagedReferenceDemoBean managedReferenceDemoBean){
        employees.add(managedReferenceDemoBean);
    }
}

The test code to test both @JsonManagedReference and @JsonBackReference annotations is:
@Test
public void testSerializingWithJsonManagedAndBackReference()
    throws JsonProcessingException {
        BackReferenceDemoBean demoBean = new BackReferenceDemoBean(123 L, "Mary Parker");
        ManagedReferenceDemoBean bean = new ManagedReferenceDemoBean(231 L, "John Thomas", demoBean);
        demoBean.addEmployees(bean);
        String jsonString = objectMapper.writeValueAsString(bean);
        System.out.println(jsonString);
        assertThat(jsonString, containsString("John Thomas"));
        assertThat(jsonString, containsString("231"));
        assertThat(jsonString, not(containsString("employees")));
}

The output of running the test in IntelliJ is:
@BackReferenceDemoBean and @ManagedReferenceDemoBean Test Output
As you can see, the field marked with @JsonManagedReference is the forward reference which will be included during serialization. The field marked with @JsonBackReference is the back reference and is usually omitted during serialization.

@JsonIdentityInfo

The @JsonIdentityInfo tells Jackson to perform serialization or deserialization using the identity of the object. This annotation works similar to the @JsonManagedReference and @JsonBackReference annotations with the difference that @JsonIdentityInfo includes the back reference object.
Let us consider an example where the IdentityInfoEmployeeDemoBean has a bidirectional relationship withIdentityInfoManagerDemoBean using the @JsonIdentityInfo annotation.

IdentityInfoEmployeeDemoBean.java

package guru.springframework.blog.jsonannotation.domain.general;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "personId")
public class IdentityInfoEmployeeDemoBean {
    public long personId = 0;
    public String  name = "James Clark";
    public IdentityInfoManagerDemoBean manager;
    public IdentityInfoEmployeeDemoBean(long personId, String name, IdentityInfoManagerDemoBean manager) {
        this.personId = personId;
        this.name = name;
        this.manager = manager;
    }
}

IdentityInfoManagerDemoBean.java

package guru.springframework.blog.jsonannotation.domain.general;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import java.util.ArrayList;
import java.util.List;
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "personId")
public class IdentityInfoManagerDemoBean {
    public long personId = 123;
    public String  name = "John Thomas";
    public List<IdentityInfoEmployeeDemoBean> employees;
    public IdentityInfoManagerDemoBean(long personId, String name) {
        this.personId = personId;
        this.name = name;
        employees = new ArrayList<IdentityInfoEmployeeDemoBean>();
    }
    public void addEmployees(IdentityInfoEmployeeDemoBean identityInfoEmployeeDemoBean){
        employees.add(identityInfoEmployeeDemoBean);
    }
}

The test code to test the @JsonIdentityInfo annotation is:
@Test
public void testSerializingWithJsonIdentityInfo()
    throws JsonProcessingException {
        IdentityInfoManagerDemoBean demoBean = new IdentityInfoManagerDemoBean(123 L, "Mary Parker");
        IdentityInfoEmployeeDemoBean bean = new IdentityInfoEmployeeDemoBean(231 L, "John Thomas", demoBean);
        demoBean.addEmployees(bean);
        String jsonString = objectMapper.writeValueAsString(bean);
        System.out.println(jsonString);
        assertThat(jsonString, containsString("John Thomas"));
        assertThat(jsonString, containsString("231"));
        assertThat(jsonString, containsString("employees"));
}

The output of running the test in IntelliJ is:
@JsonIdentityInfo Test Output
As you can see, the output gives the information about the employee with his manager details. It also provides the additional information about the employees under the manager.

@JsonFilter

The @JsonFilter annotation is used to tell Jackson to use a custom defined filter to serialize the Java object. To define your filter, you need to use the FilterProvider class. This provider gets the actual filter instance to use. The filter is then configured by assigning the FilterProvider to ObjectMapper.
Let us consider an example of Java class that uses the @JsonFilter annotation.

FilterDemoBean.java

package guru.springframework.blog.jsonannotation.domain.general;
import com.fasterxml.jackson.annotation.JsonFilter;
@JsonFilter("customFilter")
public class FilterDemoBean {
    public long personId = 123L;
    public String  name = "James Clark";
    public String gender = "male";
}

The test code to test the @JsonFilter annotation is:
@Test
public void testSerializingWithJsonFilter()
    throws JsonProcessingException {
        FilterProvider filterProvider = new SimpleFilterProvider().
        addFilter("customFilter",
            SimpleBeanPropertyFilter.filterOutAllExcept("name"));
        String jsonString = objectMapper.writer(filterProvider).
        writeValueAsString(new FilterDemoBean());
        System.out.println(jsonString);
        assertThat(jsonString, containsString("James Clark"));
        assertThat(jsonString, not(containsString("123")));
}
The output of running the test in IntelliJ is:
@JsonFilter Test Output
As you can see, the custom filter declared as the arguments of the @JsonFilter annotation extract only the name and filters out the other properties of the bean during serialization.

No comments:

Post a Comment