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
This tutorial will focus on working with tree model nodes in Jackson.
Weβll use JsonNode for various conversions as well as adding, modifying, and removing nodes.
2. Creating a Node
The first step in the creation of a node is to instantiate an ObjectMapper object by using the default constructor:
ObjectMapper mapper = new ObjectMapper();
Since the creation of an ObjectMapper object is expensive, itβs recommended that we reuse the same one for multiple operations.
Next, we have three different ways to create a tree node once we have our ObjectMapper.
2.1. Construct a Node from Scratch
This is the most common way to create a node out of nothing:
JsonNode node = mapper.createObjectNode();
Alternatively, we can also create a node via the JsonNodeFactory:
JsonNode node = JsonNodeFactory.instance.objectNode();
2.2. Parse from a JSON Source
This method is well covered in the Jackson β Marshall String to JsonNode article. Please refer to it for more info.
2.3. Convert from an Object
A node may be converted from a Java object by calling the valueToTree(Object fromValue) method on the ObjectMapper:
JsonNode node = mapper.valueToTree(fromValue);
The convertValue API is also helpful here:
JsonNode node = mapper.convertValue(fromValue, JsonNode.class);
Letβs see how it works in practice.
Assume we have a class named NodeBean:
public class NodeBean {
private int id;
private String name;
public NodeBean() {
}
public NodeBean(int id, String name) {
this.id = id;
this.name = name;
}
// standard getters and setters
}
Letβs write a test that makes sure that the conversion happens correctly:
@Test
public void givenAnObject_whenConvertingIntoNode_thenCorrect() {
NodeBean fromValue = new NodeBean(2016, "baeldung.com");
JsonNode node = mapper.valueToTree(fromValue);
assertEquals(2016, node.get("id").intValue());
assertEquals("baeldung.com", node.get("name").textValue());
}
3. Transforming a Node
3.1. Write out as JSON
This is the basic method to transform a tree node into a JSON string, where the destination can be a File, an OutputStream or a Writer:
mapper.writeValue(destination, node);
By reusing the class NodeBean declared in Section 2.3, a test makes sure this method works as expected:
final String pathToTestFile = "node_to_json_test.json";
@Test
public void givenANode_whenModifyingIt_thenCorrect() throws IOException {
String newString = "{\"nick\": \"cowtowncoder\"}";
JsonNode newNode = mapper.readTree(newString);
JsonNode rootNode = ExampleStructure.getExampleRoot();
((ObjectNode) rootNode).set("name", newNode);
assertFalse(rootNode.path("name").path("nick").isMissingNode());
assertEquals("cowtowncoder", rootNode.path("name").path("nick").textValue());
}
3.2. Convert to an Object
The most convenient way to convert a JsonNode into a Java object is the treeToValue API:
NodeBean toValue = mapper.treeToValue(node, NodeBean.class);
This is functionally equivalent to the following:
NodeBean toValue = mapper.convertValue(node, NodeBean.class)
We can also do that through a token stream:
JsonParser parser = mapper.treeAsTokens(node);
NodeBean toValue = mapper.readValue(parser, NodeBean.class);
Finally, letβs implement a test that verifies the conversion process:
@Test
public void givenANode_whenConvertingIntoAnObject_thenCorrect()
throws JsonProcessingException {
JsonNode node = mapper.createObjectNode();
((ObjectNode) node).put("id", 2016);
((ObjectNode) node).put("name", "baeldung.com");
NodeBean toValue = mapper.treeToValue(node, NodeBean.class);
assertEquals(2016, toValue.getId());
assertEquals("baeldung.com", toValue.getName());
}
4. Manipulating Tree Nodes
Weβll use the following JSON elements, contained in a file named example.json, as a base structure for actions to be taken on:
{
"name":
{
"first": "Tatu",
"last": "Saloranta"
},
"title": "Jackson founder",
"company": "FasterXML"
}
This JSON file, located on the classpath, is parsed into a model tree:
public class ExampleStructure {
private static ObjectMapper mapper = new ObjectMapper();
static JsonNode getExampleRoot() throws IOException {
InputStream exampleInput =
ExampleStructure.class.getClassLoader()
.getResourceAsStream("example.json");
JsonNode rootNode = mapper.readTree(exampleInput);
return rootNode;
}
}
Note that the root of the tree will be used when illustrating operations on nodes in the following subsections.
4.1. Locating a Node
Before working on any node, the first thing we need to do is to locate and assign it to a variable.
If we know the path to the node beforehand, thatβs pretty easy to do.
Say we want a node named last, which is under the name node:
JsonNode locatedNode = rootNode.path("name").path("last");
Alternatively, the get or with APIs can also be used instead of path.
If the path isnβt known, the search will, of course, become more complex and iterative.
We can see an example of iterating over all the nodes in Section 5 β Iterating Over the Nodes.
4.2. Adding a New Node
A node can be added as a child of another node:
ObjectNode newNode = ((ObjectNode) locatedNode).put(fieldName, value);
Many overloaded variants of put may be used to add new nodes of different value types.
Many other similar methods are also available, including putArray, putObject, PutPOJO, putRawValue and putNull.
Finally, letβs have a look at an example where we add an entire structure to the root node of the tree:
"address":
{
"city": "Seattle",
"state": "Washington",
"country": "United States"
}
Hereβs the full test going through all of these operations and verifying the results:
@Test
public void givenANode_whenAddingIntoATree_thenCorrect() throws IOException {
JsonNode rootNode = ExampleStructure.getExampleRoot();
ObjectNode addedNode = ((ObjectNode) rootNode).putObject("address");
addedNode
.put("city", "Seattle")
.put("state", "Washington")
.put("country", "United States");
assertFalse(rootNode.path("address").isMissingNode());
assertEquals("Seattle", rootNode.path("address").path("city").textValue());
assertEquals("Washington", rootNode.path("address").path("state").textValue());
assertEquals(
"United States", rootNode.path("address").path("country").textValue();
}
4.3. Editing a Node
An ObjectNode instance may be modified by invoking set(String fieldName, JsonNode value) method:
JsonNode locatedNode = locatedNode.set(fieldName, value);
Similar results might be achieved by using replace or setAll methods on objects of the same type.
To verify that the method works as expected, weβll change the value of the field name under root node from an object of first and last into another one consisting of only nick field in a test:
@Test
public void givenANode_whenModifyingIt_thenCorrect() throws IOException {
String newString = "{\"nick\": \"cowtowncoder\"}";
JsonNode newNode = mapper.readTree(newString);
JsonNode rootNode = ExampleStructure.getExampleRoot();
((ObjectNode) rootNode).set("name", newNode);
assertFalse(rootNode.path("name").path("nick").isMissingNode());
assertEquals("cowtowncoder", rootNode.path("name").path("nick").textValue());
}
4.4. Removing a Node
A node can be removed by calling the remove(String fieldName) API on its parent node:
JsonNode removedNode = locatedNode.remove(fieldName);
In order to remove multiple nodes at once, we can invoke an overloaded method with the parameter of Collection<String> type, which returns the parent node instead of the one to be removed:
ObjectNode locatedNode = locatedNode.remove(fieldNames);
In the extreme case when we want to delete all subnodes of a given node, the removeAll API comes in handy.
The following test will focus on the first method mentioned above, which is the most common scenario:
@Test
public void givenANode_whenRemovingFromATree_thenCorrect() throws IOException {
JsonNode rootNode = ExampleStructure.getExampleRoot();
((ObjectNode) rootNode).remove("company");
assertTrue(rootNode.path("company").isMissingNode());
}
5. Iterating Over the Nodes
Letβs iterate over all the nodes in a JSON document and reformat them into YAML.
JSON has three types of nodes, which are Value, Object and Array.
So, letβs ensure our sample data has all three different types by adding an Array:
{
"name":
{
"first": "Tatu",
"last": "Saloranta"
},
"title": "Jackson founder",
"company": "FasterXML",
"pets" : [
{
"type": "dog",
"number": 1
},
{
"type": "fish",
"number": 50
}
]
}
Now letβs see the YAML we want to produce:
name:
first: Tatu
last: Saloranta
title: Jackson founder
company: FasterXML
pets:
- type: dog
number: 1
- type: fish
number: 50
We know that JSON nodes have a hierarchical tree structure. So, the easiest way to iterate over the whole JSON document is to start at the top and work our way down through all the child nodes.
Weβll pass the root node into a recursive method. The method will then call itself with each child of the supplied node.
5.1. Testing the Iteration
Weβll start by creating a simple test that checks that we can successfully convert the JSON to YAML.
Our test supplies the root node of the JSON document to our toYaml method and asserts the returned value is what we expect:
@Test
public void givenANodeTree_whenIteratingSubNodes_thenWeFindExpected() throws IOException {
JsonNode rootNode = ExampleStructure.getExampleRoot();
String yaml = onTest.toYaml(rootNode);
assertEquals(expectedYaml, yaml);
}
public String toYaml(JsonNode root) {
StringBuilder yaml = new StringBuilder();
processNode(root, yaml, 0);
return yaml.toString(); }
}
5.2. Handling Different Node Types
We need to handle different types of nodes slightly differently.
Weβll do this in our processNode method:
private void processNode(JsonNode jsonNode, StringBuilder yaml, int depth) {
if (jsonNode.isValueNode()) {
yaml.append(jsonNode.asText());
}
else if (jsonNode.isArray()) {
for (JsonNode arrayItem : jsonNode) {
appendNodeToYaml(arrayItem, yaml, depth, true);
}
}
else if (jsonNode.isObject()) {
appendNodeToYaml(jsonNode, yaml, depth, false);
}
}
First, letβs consider a Value node. We simply call the asText method of the node to get a String representation of the value.
Next, letβs look at an Array node. Each item within the Array node is itself a JsonNode, so we iterate over the Array and pass each node to the appendNodeToYaml method. We also need to know that these nodes are part of an array.
Unfortunately, the node itself does not contain anything that tells us that, so weβll pass a flag into our appendNodeToYaml method.
Finally, we want to iterate over all the child nodes of each Object node. One option is to use JsonNode.elements.
However, we canβt determine the field name from an element because it just contains the field value:
Object {"first": "Tatu", "last": "Saloranta"}
Value "Jackson Founder"
Value "FasterXML"
Array [{"type": "dog", "number": 1},{"type": "fish", "number": 50}]
Instead, weβll use JsonNode.fields because this gives us access to both the field name and value:
Key="name", Value=Object {"first": "Tatu", "last": "Saloranta"}
Key="title", Value=Value "Jackson Founder"
Key="company", Value=Value "FasterXML"
Key="pets", Value=Array [{"type": "dog", "number": 1},{"type": "fish", "number": 50}]
For each field, we add the field name to the output and then process the value as a child node by passing it to the processNode method:
private void appendNodeToYaml(
JsonNode node, StringBuilder yaml, int depth, boolean isArrayItem) {
Iterator<Entry<String, JsonNode>> fields = node.fields();
boolean isFirst = true;
while (fields.hasNext()) {
Entry<String, JsonNode> jsonField = fields.next();
addFieldNameToYaml(yaml, jsonField.getKey(), depth, isArrayItem && isFirst);
processNode(jsonField.getValue(), yaml, depth+1);
isFirst = false;
}
}
We canβt tell from the node how many ancestors it has.
So, we pass a field called depth into the processNode method to keep track of this, and we increment this value each time we get a child node so that we can correctly indent the fields in our YAML output:
private void addFieldNameToYaml(
StringBuilder yaml, String fieldName, int depth, boolean isFirstInArray) {
if (yaml.length()>0) {
yaml.append("\n");
int requiredDepth = (isFirstInArray) ? depth-1 : depth;
for(int i = 0; i < requiredDepth; i++) {
yaml.append(" ");
}
if (isFirstInArray) {
yaml.append("- ");
}
}
yaml.append(fieldName);
yaml.append(": ");
}
Now that we have all the code in place to iterate over the nodes and create the YAML output, we can run our test to show that it works.
6. Conclusion
This article covered the common APIs and scenarios while working with a tree model in Jackson.
