Pages

Tuesday, May 19, 2020

Jackson API @JsonAnyGetter Annotation - Map Example

1. Introduction


In this Jackson Annotation Series, You'll be learning @JsonAnyGetter annotation and example program to demonstrate @JsonAnyGetter annotation with Map Example in Jackson API.

Jackson API is mainly integrated with Java tech stack.

Mainly @JsonAnyGetter is used on Map<String, String> property which holds the additional properties which are apart from the normal properties.

Please note that @ JsonAnyGetter is a method level annotation and can not be used on the filed level. If you use so on field level will cause a compile-time error.

This is the first annotation in our series.


Jackson API @JsonAnyGetter Annotation - Map Example



Jackson API @JsonAnyGetter


First, let us see the example program without using @JsonAnyGetter annotation and observer the output.

Furthermore, We will see the how-to flat the key-value pairs of the map as normal values.

Do not worry much about @JsonAnyGetter annotation because after seeing the examples on this, you will be in a good position to use this annotation in your application.

Add the maven dependency before using jackson annotations.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.0</version>
</dependency>

2. Java Example Without @JsonAnyGetter Annotation


First, let us see a simple example of how the map fields are shown in the JSON string format.

Table Class:

package com.javaprogramto.jackson.annotations;

import java.util.HashMap;
import java.util.Map;

public class Table {

    private int id;
    private String modelName;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getModelName() {
        return modelName;
    }

    public void setModelName(String modelName) {
        this.modelName = modelName;
    }

    public Map<String, String> getOtherProperties() {
        return otherProperties;
    }

    public void setOtherProperties(Map<String, String> otherProperties) {
        this.otherProperties = otherProperties;
    }
}

JsonAnyGetterExample

It is good to understand before using JsonAnyGetter annotation on any getter method.

package com.javaprogramto.jackson.annotations;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.HashMap;
import java.util.Map;

public class JsonAnyGetterExample {

    public static void main(String[] args) throws JsonProcessingException {

        Table table = getTable();

        ObjectMapper mapper = new ObjectMapper();
        String tableAsJson = mapper
                .writerWithDefaultPrettyPrinter()
                .writeValueAsString(table);
        System.out.println("Table as json : "+tableAsJson);


    }

    private static Table getTable() {

        Table table = new Table();

        table.setId(100);
        table.setModelName("Laptop Table");

        Map<String, String> otherProperties = new HashMap<>();

        otherProperties.put("NoOfLegs", "4");
        otherProperties.put("PreferredColor", "White");

        table.setOtherProperties(otherProperties);

        return table;
    }

}

Output:

Table as json : {

  "id" : 100,

  "modelName" : "Laptop Table",

  "otherProperties" : {

    "NoOfLegs" : "4",

    "PreferredColor" : "White"

  }

}

3. Jackson Map Example @JsonAnyGetter Annotation


If you look at generated JSON content it has treated the map properties as separate fields under "otherProperties".

But whenever we added the properties are actually direct properties of the Table. If we can get all of the map fields directly it looks like one entity.

Use JsonAnyGetter annotation on the map getter method so that it can do flat the map fields.

Let us change the Table class and run the main method.

Table class:

@JsonAnyGetter

public Map<String, String> getOtherProperties() {
    return otherProperties;
}
public void setOtherProperties(Map<String, String> otherProperties) {
    this.otherProperties = otherProperties;
}

Next, Run the same main program which we have implemented in the above section, and that produces the following output.

Table as json : {

  "id" : 100,

  "modelName" : "Laptop Table",

  "NoOfLegs" : "4",

  "PreferredColor" : "White"

}

Finally, We have put together the other properties directly under the root table element and removed the "otherProperties" attribute from the JSON content.

4. Using @JsonAnyGetter on Multiple Getters


If you try to use @JsonAnyGetter annotation on multiple getters then it will throw runtime exception "JsonMappingException - Multiple 'any-getters' defined"

@JsonAnyGetter
public String getModelName() {
    return modelName;
}

public void setModelName(String modelName) {
    this.modelName = modelName;
}

@JsonAnyGetter
public Map<String, String> getOtherProperties() {
    return otherProperties;
}


Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Problem with definition of [AnnotedClass com.javaprogramto.jackson.annotations.Table]: Multiple 'any-getters' defined ([method com.javaprogramto.jackson.annotations.Table#getModelName(0 params)] vs [method com.javaprogramto.jackson.annotations.Table#getOtherProperties(0 params)])

 at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:295)

 at com.fasterxml.jackson.databind.SerializerProvider.reportMappingProblem(SerializerProvider.java:1309)

 at com.fasterxml.jackson.databind.SerializerProvider._createAndCacheUntypedSerializer(SerializerProvider.java:1427)

 at com.fasterxml.jackson.databind.SerializerProvider.findValueSerializer(SerializerProvider.java:521)

 at com.fasterxml.jackson.databind.SerializerProvider.findTypedValueSerializer(SerializerProvider.java:799)

 at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:308)

 at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1513)

 at com.fasterxml.jackson.databind.ObjectWriter._configAndWriteValue(ObjectWriter.java:1215)

 at com.fasterxml.jackson.databind.ObjectWriter.writeValueAsString(ObjectWriter.java:1085)

 at com.javaprogramto.jackson.annotations.JsonAnyGetterExample.main(JsonAnyGetterExample.java:19)

Caused by: java.lang.IllegalArgumentException: Problem with definition of [AnnotedClass com.javaprogramto.jackson.annotations.Table]: Multiple 'any-getters' defined ([method com.javaprogramto.jackson.annotations.Table#getModelName(0 params)] vs [method com.javaprogramto.jackson.annotations.Table#getOtherProperties(0 params)])

 at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.reportProblem(POJOPropertiesCollector.java:1108)

 at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.getAnyGetter(POJOPropertiesCollector.java:225)

 at com.fasterxml.jackson.databind.introspect.BasicBeanDescription.findAnyGetter(BasicBeanDescription.java:471)

 at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.constructBeanOrAddOnSerializer(BeanSerializerFactory.java:419)

 at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.findBeanOrAddOnSerializer(BeanSerializerFactory.java:286)

 at com.fasterxml.jackson.databind.ser.BeanSerializerFactory._createSerializer2(BeanSerializerFactory.java:231)

 at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.createSerializer(BeanSerializerFactory.java:165)

 at com.fasterxml.jackson.databind.SerializerProvider._createUntypedSerializer(SerializerProvider.java:1474)

 at com.fasterxml.jackson.databind.SerializerProvider._createAndCacheUntypedSerializer(SerializerProvider.java:1422)

 ... 7 more


5. @JsonAnyGetter on Setter Method


If you use it on the setter method that works similar to the without annotation on the getter method. this produces the output as same as without using @JsonAnyGetter annotation. It makes no impact on serialization when using it on the setter method.

Sometimes you see that JsonAnyGetter not working properly if you apply on the wrong method.

@JsonAnyGetter
public void setOtherProperties(Map<String, String> otherProperties) {
    this.otherProperties = otherProperties;
}


6. Conclusion


In conclusion, We've seen how to use @JsonAnyGetter on the map fields. If you want to bring the map fields under its parent or root element than that map field getter method should be annotated with @JsonAnyGetter annotation.

In the next article, you will see how to deserialize the map objects from JSON to Object with JsonAnySetter annotation.

All the code shown in this article is over GitHub.

Please leave your questions and comments on the comments section.

If you like the article, please share with your friends.

No comments:

Post a Comment

Please do not add any spam links in the comments section.