I just released Module 10 in the Master Class of “REST with Spring”:

>> THE "REST WITH SPRING" CLASSES

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 creation of a node is to instantiate an ObjectMapper object by using the default constructor:

ObjectMapper mapper = new ObjectMapper();

Since creation of an ObjectMapper object is expensive, it’s recommended that the same one should be reused 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

The most common way to create a node out of nothing is as follows:

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 if you need 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 us 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

The basic method to transform a tree node into a JSON string is the following:

mapper.writeValue(destination, node);

where the destination can be a File, an OutputStream or a Writer.
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);

Which is functionally equivalent to:

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

The following JSON elements, contained in a file named example.json, are used as a base structure for actions discussed in this section 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 sub-sections.

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 the path to the node is known beforehand, that’s pretty easy to do. For example, 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.

4.2. Adding a New Node

A node can be added as a child of another node as follows:

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 will change 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. Conclusion

This tutorial covered the common APIs and scenarios of working with a tree model in Jackson.

The implementation of all these examples and code snippets can be found in my github project – this is an Eclipse based project, so it should be easy to import and run as it is.

Go deeper into building a REST API with Spring:

>> CHECK OUT THE COURSE

  • Rishi

    Very well explained. Thanks.
    How would you get a path(or key) hierarchy. Say for following Json:
    {
    “contactDetails”: [
    {
    “title”: “Jackson founder”,
    “company”: “FasterXML”,
    “name”:
    {
    “first”: “Tatu”,
    “last”: “Saloranta”
    },
    “contactNumber”:
    {
    // edited out
    }
    }
    ]
    }

    How could I get the following text:
    contactDetails[1].contactNumber.phone.landline OR contactDetails[1]/contactNumber/phone/landline
    ( dot or / is just a separator)
    Kindly help.

    • Hey Rishi – this looks like an interesting piece of JSON to parse; I’m editing out most of the example (to long for the comments section) – but feel free to open a PR with a failing test on the main repo of the article and I’ll be happy to have a look.
      Cheers,
      Eugen.