In this brief article, you'll learn how to map a dynamic JSON object to a Java class using Jackson. It is easier to work with predefined JSON structures while using Jackson. However, the parser will fail when the JSON data contains unknown properties. We can either choose to ignore unknown properties or map them directly to Java class.

Jackson provides multiple ways to handle the mapping of dynamic JSON object into Java classes. Suppose, we have the following JSON object:

{
  "name": "John Doe",
  "email": "john.doe@example.com",
  "roles": [
    "Member",
    "Admin"
  ],
  "admin": true,
  "address": {
    "city": "New York City",
    "state": "New York",
    "zipCode": 66123,
    "country": "US"
  }
}

Now we want to map the above JSON object to the following Java class called User:

public class User {

    public String name;
    public String email;
    private String[] roles;
    private boolean admin;

    public User() {
    }

    public User(String name, String email, String[] roles, boolean admin) {
        this.name = name;
        this.email = email;
        this.roles = roles;
        this.admin = admin;
    }

    // getters and setters, toString() .... (omitted for brevity)
}

As you can see above, the User class contains most of the common JSON properties except address. We want to find an appropriate way to map this address property to a User object.

Mapping Dynamic Properties using JsonNode

The simplest and straightforward way of mapping dynamic JSON properties is to use the JsonNode class provided by Jackson. This class is capable of handling dynamic properties.

To use JsonNode, we need to specify it as a field in our User class, as shown below:

public class User {

    public String name;
    public String email;
    private String[] roles;
    private boolean admin;
    private JsonNode address;
        
    // ...
}

Finally, parse the above JSON object to verify that it works:

try {
    // JSON string
    String json = "{\"name\":\"John Doe\",\"email\":\"john.doe@example.com\"," +
            "\"roles\":[\"Member\",\"Admin\"],\"admin\":true,\"address\":{\"city\"" +
            ":\"New York City\",\"state\":\"New York\",\"zipCode\":66123," +
            "\"country\":\"US\"}}";

    // create object mapper instance
    ObjectMapper mapper = new ObjectMapper();

    // convert JSON string to Java Object
    User user = mapper.readValue(json, User.class);

    // print user object
    System.out.println(user);

    // get properties from address
    System.out.println(user.getAddress().path("city").asText());
    System.out.println(user.getAddress().path("state").asText());

} catch (Exception ex) {
    ex.printStackTrace();
}

You should see the following output printed on the console:

User{name='John Doe', email='john.doe@example.com', roles=[Member, Admin], admin=true, 
address={"city":"New York City","state":"New York","zipCode":66123,"country":"US"}}
New York City
New York

The above solution works fine but we are now dependent on the Jackson library as we have a JsonNode field.

Mapping Dynamic Properties using Map

Another way of storing the dynamic JSON property like address is to use the Java Map collection. This will also remove the extra Jackson dependency.

Just change the address field data type to Map<String, Object> in the User class:

public class User {

    public String name;
    public String email;
    private String[] roles;
    private boolean admin;
    private Map<String, Object> address;
    
        // ...
}

Let us now parse the above JSON object to see how it works:

try {
    // JSON string
    String json = "{\"name\":\"John Doe\",\"email\":\"john.doe@example.com\"," +
            "\"roles\":[\"Member\",\"Admin\"],\"admin\":true,\"address\":{\"city\"" +
            ":\"New York City\",\"state\":\"New York\",\"zipCode\":66123," +
            "\"country\":\"US\"}}";

    // create object mapper instance
    ObjectMapper mapper = new ObjectMapper();

    // convert JSON string to Java Object
    User user = mapper.readValue(json, User.class);

    // print user object
    System.out.println(user);

    // get properties from address
    System.out.println(user.getAddress().get("city"));
    System.out.println(user.getAddress().get("country"));

} catch (Exception ex) {
    ex.printStackTrace();
}

The above example will print the following on the console:

User{name='John Doe', email='john.doe@example.com', roles=[Member, Admin], admin=true, 
address={city=New York City, state=New York, zipCode=66123, country=US}}
New York City
US

Mapping Dynamic Properties using @JsonAnySetter Annotation

The previous solutions are good when the JSON object or a nested object only contains dynamic properties. However, sometimes we have a more complex situation where the JSON object contains both dynamic and fixed properties.

Consider the following JSON object with two top-level unknown properties, city and country:

{
  "name": "John Doe",
  "email": "john.doe@example.com",
  "roles": [
    "Member",
    "Admin"
  ],
  "admin": true,
  "city": "New York City",
  "country": "United States"
}

We can easily handle this kind of JSON structure as a dynamic JSON object. But it means that we cannot define known properties like name, email, etc. — we have to treat them as dynamic properties too.

For such use cases, Jackson provides the @JsonAnySetter annotation to specify a method for handling all unknown properties. This method accepts two parameters: the name and the value of the property:

public class User {

    public String name;
    public String email;
    private String[] roles;
    private boolean admin;
    private Map<String, Object> address = new HashMap<>();

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

    // ...
}

Since we are using a Map to store dynamic properties, the JSON parsing code is same as the above:

try {
    // JSON string
    String json = "{\"name\":\"John Doe\",\"email\":\"john.doe@example.com\"," +
            "\"roles\":[\"Member\",\"Admin\"],\"admin\":true,\"city\"" +
            ":\"New York City\",\"country\":\"United States\"}";

    // create object mapper instance
    ObjectMapper mapper = new ObjectMapper();

    // convert JSON string to Java Object
    User user = mapper.readValue(json, User.class);

    // print user object
    System.out.println(user);

    // get properties from address
    System.out.println(user.getAddress().get("city"));
    System.out.println(user.getAddress().get("country"));

} catch (Exception ex) {
    ex.printStackTrace();
}

You should see the following output:

User{name='John Doe', email='john.doe@example.com', roles=[Member, Admin], admin=true,
address={country=United States, city=New York City}}
New York City
United States

For more Jackson examples, check out the How to read and write JSON using Jackson in Java tutorial.

✌️ Like this article? Follow @attacomsian on Twitter. You can also follow me on LinkedIn and DEV. Buy me a coffee (cost $3)

Need help to start a new Spring Boot or MEAN stack project? I am available for contract work. Hire me to accomplish your business goals with engineering and design. Let’s talk about your project: hi@attacomsian.com.