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 quick tutorial, we’ll learn multiple ways of mapping dynamic JSON objects into Java classes.

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

2. Using JsonNode

Let’s say we want to process product specifications in a web shop. All the products have some common properties, but they have different ones as well, depending 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, there’s 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 options 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 this kind of structure as a dynamic object. Unfortunately, this 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 that’s a more complex topic, we cover it in a different article, getting Started with Custom Deserialization in Jackson.

6. Conclusion

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

As usual, the examples are available over on GitHub.