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. 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 tutorial, 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
Further reading:
Using Optional with Jackson
Inheritance with Jackson
Using @JsonComponent in Spring Boot
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.17.2</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. 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 article, 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.
