Jackson and JSON in Java, finally learn with a coding-first approach:
>> Download the eBookMocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
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.
Further reading:
Mapping Nested Values with Jackson
Using Optional with Jackson
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.
