Open In App

Mapping a Dynamic JSON Object Field in Jackson with Java

Improve
Improve
Like Article
Like
Save
Share
Report

All REST API services are providing the resultant output either as XML or JSON pattern only as both are portable in nature and among that JSON is more popular. We can expect unknown properties in a JSON object. There are different ways available to map dynamic JSON objects into Java classes. They are obviously required in many scenarios. Let us cover this with a sample maven project.

Example Project

A maven project has to be created by adding the dependencies as specified in pom.xml

<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20220320</version>
</dependency>

Project Structure:

Project Structure

 

As this is the maven project, all the dependencies need to be seen in pom.xml

pom.xml

XML




<?xml version="1.0" encoding="UTF-8"?>
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                        http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>gfg-jackson-conversions</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gfg-jackson-conversions</name>
  
    <parent>
        <groupId>com.gfg</groupId>
        <artifactId>jackson-modules</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
  
    <dependencies>
   
        <dependency>
            <groupId>org.mock-server</groupId>
            <artifactId>mockserver-netty</artifactId>
            <version>${mockserver-netty.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>${json.version}</version>
        </dependency>
    </dependencies>
  
    <build>
        <finalName>gfg-jackson-conversions</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
  
    <properties>
        <mockserver-netty.version>5.13.2</mockserver-netty.version>
        <json.version>20220320</json.version>
    </properties>
  
</project>


By seeing different examples, let us cover the concept. Suppose, we have a dynamic JSON of this type

{
    "productName": "Samsung M30",
    "productCategory": "cellphone",
    "productDetails": {
        "displayAspectRatio": "97:3",
        "audioConnector": "available"
    }
}

In the above JSON, “displayAspectRatio” and “audioConnector” are applicable to a mobilephone but not a shoe or any foodItem.

1. By using com.fasterxml.jackson.databind.JsonNode

com.fasterxml.jackson.databind.JsonNode can handle dynamic keys and hence in our java class, we can make the productDetails as JsonNode

OnlineShoppingProductJsonNode.java

Java




import com.fasterxml.jackson.databind.JsonNode;
  
public class OnlineShoppingProductJsonNode {
  
    private String productName;
    private String productCategory;
    // As JsonNode support dynamic key, 
    // it is done like this
    private JsonNode productDetails; 
  
    public String getProductName() {
        return productName;
    }
  
    public void setProductName(String productName) {
        this.productName = productName;
    }
  
    public String getProductCategory() {
        return productCategory;
    }
  
    public void setProductCategory(String productCategory) {
        this.productCategory = productCategory;
    }
  
    public JsonNode getProductDetails() {
        return productDetails;
    }
  
    public void setProductDetails(JsonNode productDetails) {
        this.productDetails = productDetails;
    }
  
}


Note: dependency on Jackson library is needed if we use JsonNode

Hence during testing, we need to write as

@Test
void jsonString_parsingToJsonNode_resultWillBeDynamicProperties() throws JsonParseException, JsonMappingException, IOException {
        // given
        String givenJson = readJsonResource("/sample-dynamic-object/embedded.json");

        // when
        OnlineShoppingProductJsonNode product = objectMapper.readValue(givenJson, OnlineShoppingProductJsonNode.class);

        // then
        assertThat(product.getProductName()).isEqualTo("Samsung M30");
        assertThat(product.getProductCategory()).isEqualTo("cellphone");
        assertThat(product.getProductDetails().get("audioConnector").asText()).isEqualTo("available");
    }

Instead of depending on Jackson library, it can be made by using

2. By using java.util.Map

It can be done by using Map<String, Object>

OnlineShoppingProductMap,java

Java




import java.util.Map;
  
public class OnlineShoppingProductMap {
  
    private String productName;
    private String productCategory;
    private Map<String, Object> productDetails;
  
    public String getProductName() {
        return productName;
    }
  
    public void setProductName(String name) {
        this.productName = name;
    }
  
    public String getProductCategory() {
        return productCategory;
    }
  
    public void setProductCategory(String category) {
        this.productCategory = category;
    }
  
    public Map<String, Object> getProductDetails() {
        return productDetails;
    }
  
    public void setProductDetails(Map<String, Object> productDetails) {
        this.productDetails = productDetails;
    }
  
}


Product Details need to be stored in Map<String,Object>. Hence testing is easier i.e. no need to depend on Jackson library

@Test
void jsonString_parsingToMap_resultWillBeDynamicProperties() throws JsonParseException, JsonMappingException, IOException {
        // given
        String givenJson = readJsonResource("/sample-dynamic-object/embedded.json");

        // when
        OnlineShoppingProductMap product = objectMapper.readValue(givenJson, OnlineShoppingProductMap.class);

        // then
        assertThat(product.getProductName()).isEqualTo("Samsung M30");
        assertThat(product.getProductCategory()).isEqualTo("cellphone");
        assertThat(product.getProductDetails().get("audioConnector")).isEqualTo("available");
    }

3. By using @JsonAnySetter

When fixed and dynamic properties are there in JSON, then we need to go for @JsonAnySetter. @JsonAnySetter is used to mark a method for handling additional, unknown properties and should contain the name and value of the property.

@JsonAnySetter
public void setProductDetails(String key, Object value) {
        productDetails.put(key,value);
}

Example JSON

// We have fixed and dynamic properties
{ 
    "productName": "Samsung M30",
    "productCategory": "cellphone",
    "displayAspectRatio": "97:3",
    "audioConnector": "available"
}

OnlineShoppingProduct.java

Java




import java.util.LinkedHashMap;
import java.util.Map;
  
import com.fasterxml.jackson.annotation.JsonAnySetter;
  
public class OnlineShoppingProduct {
  
    private String productName;
    private String productCategory;
    private Map<String, Object> productDetails = new LinkedHashMap<>();
    public String getProductName() {
        return productName;
    }
  
    public void setProductName(String productName) {
        this.productName = productName;
    }
  
    public String getProductCategory() {
        return productCategory;
    }
  
    public void setProductCategory(String productCategory) {
        this.productCategory = productCategory;
    }
  
    public Map<String, Object> getProductDetails() {
        return productDetails;
    }
    
    @JsonAnySetter
    public void setProductDetails(String key, Object value) {
        productDetails.put(key,value);
    }
}


Let us assume that we have embedded json and flat json available in a particular folder called “test/resources/sample-dynamic-object

embedded json

{
    "productName": "Samsung M30",
    "productCategory": "cellphone",
    "productDetails": {
        "displayAspectRatio": "97:3",
        "audioConnector": "available"
    }
}

flat json

{
    "productName": "Samsung M30",
    "productCategory": "cellphone",
    "displayAspectRatio": "97:3",
    "audioConnector": "available"
}

Let us write the test file

GFGDynamicObjectDeserializationSampleUnitTest.java

For the above two JSONs, our test scenarios pass

Java




import static org.assertj.core.api.Assertions.assertThat;
  
import java.io.IOException;
import java.util.Scanner;
  
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
  
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
  
public class GFGDynamicObjectDeserializationSampleUnitTest {
  
    private ObjectMapper objectMapper;
  
    @BeforeEach
    void setup() {
        objectMapper = new ObjectMapper();
    }
  
    private String readJsonResource(String jsonPath) {
        try (Scanner scanner = new Scanner(getClass().getResourceAsStream(jsonPath), "UTF-8")) {
            return scanner.useDelimiter("\\A").next();
        }
    }
  
    @Test
    void jsonString_parsingToJsonNode_resultWillBeDynamicProperties() throws JsonParseException, JsonMappingException, IOException {
        // given
        String givenJson = readJsonResource("/sample-dynamic-object/embedded.json");
  
        // when
        OnlineShoppingProductJsonNode product = objectMapper.readValue(givenJson, OnlineShoppingProductJsonNode.class);
  
        // then
        assertThat(product.getProductName()).isEqualTo("Samsung M30");
        assertThat(product.getProductCategory()).isEqualTo("cellphone");
        assertThat(product.getProductDetails().get("audioConnector").asText()).isEqualTo("available");
    }
  
    @Test
    void jsonString_parsingToMap_resultWillBeDynamicProperties() throws JsonParseException, JsonMappingException, IOException {
        // given
        String givenJson = readJsonResource("/sample-dynamic-object/embedded.json");
  
        // when
        OnlineShoppingProductMap product = objectMapper.readValue(givenJson, OnlineShoppingProductMap.class);
  
        // then
        assertThat(product.getProductName()).isEqualTo("Samsung M30");
        assertThat(product.getProductCategory()).isEqualTo("cellphone");
        assertThat(product.getProductDetails().get("audioConnector")).isEqualTo("available");
    }
  
    @Test
    void jsonString_ParsingWithJsonAnySetter_resultWillBeDynamicProperties() throws JsonParseException, JsonMappingException, IOException {
        // given
        String givenJson = readJsonResource("/sample-dynamic-object/flat.json");
  
        // when
        OnlineShoppingProduct product = objectMapper.readValue(givenJson, OnlineShoppingProduct.class);
  
        // then
        assertThat(product.getProductName()).isEqualTo("Samsung M30");
        assertThat(product.getProductCategory()).isEqualTo("cellphone");
        assertThat(product.getProductDetails().get("audioConnector")).isEqualTo("available");
    }
}


When run as JUNIT Test, then

 



Last Updated : 21 Dec, 2022
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads