Jackson Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

Working with predefined JSON data structures with Jackson is straightforward. However, sometimes we need to handle dynamic JSON objects, which have unknown properties.

In this short tutorial, we’ll see multiple ways of mapping dynamic JSON objects into Java classes.

Note that in all of the tests, we assume we have a field objectMapper of type com.fasterxml.jackson.databind.ObjectMapper.

2. Using JsonNode

Let’s say we want to process product specifications in a webshop. All products have some common properties, but there’re others, which depend on the type of the product.

For example, we want to know the aspect ratio of the display of a cell phone, but this property doesn’t make much sense for a shoe.

The data structure looks like this:

{
    "name": "Pear yPhone 72",
    "category": "cellphone",
    "details": {
        "displayAspectRatio": "97:3",
        "audioConnector": "none"
    }
}

We store the dynamic properties in the details object.

We can map the common properties with the following Java class:

class Product {

    String name;
    String category;

    // standard getters and setters
}

On top of that, we need an appropriate representation for the details object. For example, com.fasterxml.jackson.databind.JsonNode can handle dynamic keys.

To use it, we have to add it as a field to our Product class:

class Product {

    // common fields

    JsonNode details;

    // standard getters and setters
}

Finally, we verify that it works:

String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector").asText()).isEqualTo("none");

However, we have a problem with this solution. Our class depends on the Jackson library since we have a JsonNode field.

3. Using Map

We can solve this issue by using java.util.Map for the details field. More precisely, we have to use Map<String, Object>.

Everything else can stay the same:

class Product {

    // common fields

    Map<String, Object> details;

    // standard getters and setters
}

And then we can verify it with a test:

String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

4. Using @JsonAnySetter

The previous solutions are good when an object contains only dynamic properties. However, sometimes we have fixed and dynamic properties mixed in a single JSON object.

For example, we may need to flatten our product representation:

{
    "name": "Pear yPhone 72",
    "category": "cellphone",
    "displayAspectRatio": "97:3",
    "audioConnector": "none"
}

We can treat a structure like this as a dynamic object. Unfortunately, that means we can’t define common properties — we have to treat them dynamically, too.

Alternatively, we could use @JsonAnySetter to mark a method for handling additional, unknown properties. Such a method should accept two arguments: the name and value of the property:

class Product {

    // common fields

    Map<String, Object> details = new LinkedHashMap<>();

    @JsonAnySetter
    void setDetail(String key, Object value) {
        details.put(key, value);
    }

    // standard getters and setters
}

Note, that we have to instantiate the details object to avoid NullPointerExceptions.

Since we store the dynamic properties in a Map, we can use it the same way we did before:

String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

5. Creating a Custom Deserializer

For most cases, these solutions work just fine. However, sometimes we need much more control. For example, we could store deserialization information about our JSON objects in a database.

We can target those situations with a custom deserializer. Since it’s a complex topic, we cover it in a different article, getting Started with Custom Deserialization in Jackson.

6. Conclusion

In this article, we saw multiple ways of handling dynamic JSON objects with Jackson.

As usual, the examples are available over on GitHub.

Jackson bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE