Mapping Nested Values with Jackson

1. Overview

A typical use case when working with JSON is to perform a transformation from one model into another. For example, we might want to parse a complex, densely nested object graph into a more straightforward model for use in another domain.

In this quick article, we’ll look at how to map nested values with Jackson to flatten out a complex data structure. We’ll deserialize JSON in three different ways:

  • Using@JsonProperty
  • Using JsonNode
  • Using a custom JsonDeserializer

2. Maven Dependency

Let’s first add the following dependency to pom.xml:

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

We can find the latest versions of jackson-databind on Maven Central.

3. JSON Source

Consider the following JSON as the source material for our examples. While the structure is contrived, note that we include properties that are nested two levels deep:

{
    "id": "957c43f2-fa2e-42f9-bf75-6e3d5bb6960a",
    "name": "The Best Product",
    "brand": {
        "id": "9bcd817d-0141-42e6-8f04-e5aaab0980b6",
        "name": "ACME Products",
        "owner": {
            "id": "b21a80b1-0c09-4be3-9ebd-ea3653511c13",
            "name": "Ultimate Corp, Inc."
        }
    }  
}

4. Simplified Domain Model

In a flattened domain model described by the Product class below, we’ll extract brandName, which is nested one level deep within our source JSON. Also, we’ll extract ownerName, which is nested two levels deep and within the nested brand object:

public class Product {

    private String id;
    private String name;
    private String brandName;
    private String ownerName;

    // standard getters and setters
}

5. Mapping With Annotations

To map the nested brandName property, we first need to unpack the nested brand object to a Map and extract the name property. Then to map ownerName, we unpack the nested owner object to a Map and extract its name property.

We can instruct Jackson to unpack the nested property by using a combination of @JsonProperty and some custom logic that we add to our Product class:

public class Product {
    // ...

    @SuppressWarnings("unchecked")
    @JsonProperty("brand")
    private void unpackNested(Map<String,Object> brand) {
        this.brandName = (String)brand.get("name");
        Map<String,String> owner = (Map<String,String>)brand.get("owner");
        this.ownerName = owner.get("name");
    }
}

Our client code can now use an ObjectMapper to transform our source JSON, which exists as the String constant SOURCE_JSON within the test class:

@Test
public void whenUsingAnnotations_thenOk() throws IOException {
    Product product = new ObjectMapper()
      .readerFor(Product.class)
      .readValue(SOURCE_JSON);

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

6. Mapping With JsonNode

Mapping a nested data structure with JsonNode requires a little more work. Here, we use ObjectMapper‘s readTree to parse out the desired fields:

@Test
public void whenUsingJsonNode_thenOk() throws IOException {
    JsonNode productNode = new ObjectMapper().readTree(SOURCE_JSON);

    Product product = new Product();
    product.setId(productNode.get("id").textValue());
    product.setName(productNode.get("name").textValue());
    product.setBrandName(productNode.get("brand")
      .get("name").textValue());
    product.setOwnerName(productNode.get("brand")
      .get("owner").get("name").textValue());

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

7. Mapping With Custom JsonDeserializer

Mapping a nested data structure with a custom JsonDeserializer is identical to the JsonNode approach from an implementation point of view. We first create the JsonDeserializer:

public class ProductDeserializer extends StdDeserializer<Product> {

    public ProductDeserializer() {
        this(null);
    }

    public ProductDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Product deserialize(JsonParser jp, DeserializationContext ctxt) 
      throws IOException, JsonProcessingException {
 
        JsonNode productNode = jp.getCodec().readTree(jp);
        Product product = new Product();
        product.setId(productNode.get("id").textValue());
        product.setName(productNode.get("name").textValue());
        product.setBrandName(productNode.get("brand")
          .get("name").textValue());
        product.setOwnerName(productNode.get("brand").get("owner")
          .get("name").textValue());		
        return product;
    }
}

7.1. Manual Registration of Deserializer

To manually register our custom deserializer, our client code must add the JsonDeserializer to a Module, register the Module with an ObjectMapper, and call readValue:

@Test
public void whenUsingDeserializerManuallyRegistered_thenOk()
 throws IOException {
 
    ObjectMapper mapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addDeserializer(Product.class, new ProductDeserializer());
    mapper.registerModule(module);

    Product product = mapper.readValue(SOURCE_JSON, Product.class);
 
    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

7.2. Automatic Registration of Deserializer

As an alternative to the manual registration of the JsonDeserializer, we can register the deserializer directly on the class:

@JsonDeserialize(using = ProductDeserializer.class)
public class Product {
    // ...
}

With this approach, there is no need to register manually. Let’s take a look at our client code using automatic registration:

@Test
public void whenUsingDeserializerAutoRegistered_thenOk()
  throws IOException {
 
    ObjectMapper mapper = new ObjectMapper();
    Product product = mapper.readValue(SOURCE_JSON, Product.class);

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

8. Conclusion

In this tutorial, we demonstrated several ways of using Jackson to parse JSON containing nested values. Have a look at our main Jackson Tutorial page for more examples.

And, as always, code snippets, can be found over on GitHub.

Related posts:

Spring Data – CrudRepository save() Method
ETags for REST with Spring
Derived Query Methods in Spring Data JPA Repositories
Phương thức tham chiếu trong Java 8 – Method References
Java Program to Check whether Graph is a Bipartite using DFS
Giới thiệu Google Guice – Aspect Oriented Programming (AOP)
Spring REST API + OAuth2 + Angular
Using the Not Operator in If Conditions in Java
Java Program to Give an Implementation of the Traditional Chinese Postman Problem
Check If a File or Directory Exists in Java
Java Program to Generate All Subsets of a Given Set in the Gray Code Order
Guide to Guava Table
Spring Boot - Securing Web Applications
Guide to ThreadLocalRandom in Java
Quản lý bộ nhớ trong Java với Heap Space vs Stack
Spring Boot - Runners
How to Set TLS Version in Apache HttpClient
Java Program to Implement Double Ended Queue
Java Program to Solve the 0-1 Knapsack Problem
Loại bỏ các phần tử trùng trong một ArrayList như thế nào trong Java 8?
Tạo số và chuỗi ngẫu nhiên trong Java
Hướng dẫn Java Design Pattern – Dependency Injection
Dockerizing a Spring Boot Application
Java Program to Check Whether a Weak Link i.e. Articulation Vertex Exists in a Graph
The SpringJUnitConfig and SpringJUnitWebConfig Annotations in Spring 5
Java Program to Implement Hopcroft Algorithm
Java Program to Find the Edge Connectivity of a Graph
Java Program to Implement Hash Tables Chaining with List Heads
Chuyển đổi từ HashMap sang ArrayList
Server-Sent Events in Spring
Spring Boot - Admin Client
Java Program to Implement Min Heap