How to Process JSON with Jackson

How to Process JSON with Jackson

7 Comments

Last Updated on October 21, 2024 by jt

It’s not uncommon for computers to need to communicate with each other. In the early days, this was done with simple string messages. Which was problematic. There was no standard language. XML evolved to address this and provides a very structured way of sharing data between systems. XML is so structured, but many find it too restrictive.

JSON is a popular alternative to XML. It offers a lighter and more forgiving syntax than XML. It is a text-based data interchange format that is lightweight, language independent, and easy for humans to read and write.

In the current enterprise, JSON is used for enterprise messaging, communicating with RESTful web services, and AJAX-based communications. It is also extensively used by NoSQL database such as MongoDB, Oracle NoSQL Database, and Oracle Berkeley DB to store records as JSON documents. Traditional relational databases, such as PostgreSQL, are also constantly gaining more JSON capabilities. Oracle Database also supports JSON data natively with features, such as transactions, indexing, declarative querying, and views.

In Java development, you will often need to read in JSON data, or provide JSON data as an output. You could, of course, do this on your own or use an open source implementation. For Java developers, there are several options to choose from. Jackson is very popular choice for processing JSON data in Java.

Maven Dependencies for Jackson

The Jackson library is composed of three components: Jackson Databind, Core, and Annotation. Jackson Databind has internal dependencies on Jackson Core and Annotation. Therefore, adding Jackson Databind to your Maven POM dependency list will include the other dependencies as well.

. . .
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
       <version>2.7.4</version>
</dependency>
.  .  .

Spring Boot and Jackson

The above dependency declaration will work for other Java projects, but in a Spring Boot application, you may encounter errors such as this.

Jackson Dependency Conflict Error - NoSuchMethod Error

The Spring Boot parent POM includes Jackson dependencies. When you include the version number, it overrides the Spring Boot curated dependency versions. Therefore, you may encounter version conflicts.

The proper way for Jackson dependency declaration is to use the Spring Boot curated dependency and not including the version tag on the main Jackson library. Here is an example:

. . .
<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
</dependency>
. . .

NOTE: This problem is highly dependent on the version of Spring Boot you are using.

For more details on this issue, check out my post Jackson Dependency Issue in Spring Boot with Maven Build.

Reading JSON – Data Binding in Jackson

Data binding is a JSON processing model that allows for seamless conversion between JSON data and Java objects. With data binding, you create POJOs following JavaBeans convention with properties corresponding to the JSON data. The Jackson ObjectMapper is responsible for mapping the JSON data to the POJOs. To understand how the mapping happens, let’s create a JSON file representing data of an employee.

employee.json

   //{
  "id": 123,
  "name": "Henry Smith",
  "age": 28,
  "salary": 2000,
  "designation": "Programmer",
  "address": {
    "street": "Park Avn.",
    "city": "Westchester",
    "zipcode": 10583
  },
  "phoneNumbers": [
    654321,
    222333
  ],
  "personalInformation": {
    "gender": "Male",
    "maritialstatus": "Married"
  }
}

The preceding JSON is composed of several JSON objects with name-value pairs and a phoneNumbers array. Based on the JSON data, we’ll create two POJOs: Address and Employee. The Employee object will be composed of Address and will contain properties with getter and setter method corresponding to the JSON constructs.

When Jackson maps JSON to POJOs, it inspects the setter methods. Jackson, by default, maps a key for the JSON field with the setter method name. For Example, Jackson will map the name JSON field with the setName() setter method in a POJO.

With these rules in mind, let’s write the POJOs.

Address.java

   //package guru.springframework.blog.jsonwithjackson.domain;

public class Address {
    private String street;
    private String city;
    private int zipCode;

    public String getStreet() {
        return street;
    }
    public void setStreet(String street) {
        this.street = street;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public int getZipCode() {
        return zipCode;
    }
    public void setZipcode(int zipcode) {
        this.zipCode = zipcode;
    }

    @Override
    public String toString(){
        return getStreet() + ", "+getCity()+", "+getZipCode();
    }
}

Employee.java

   //package guru.springframework.blog.jsonwithjackson.domain;


import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Map;

public class Employee {

    private int id;
    private String name;
    private int age;
    private BigDecimal salary;
    private String designation;
    private Address address;
    private long[] phoneNumbers;
    private Map<String, String> personalInformation;

    /*Getter and Setter Methods*/
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public BigDecimal getSalary() {
        return salary;
    }

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }

    public String getDesignation() {
        return designation;
    }

    public void setDesignation(String designation) {
        this.designation = designation;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public long[] getPhoneNumbers() {
        return phoneNumbers;
    }

    public void setPhoneNumbers(long[] phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

    public Map<String, String> getPersonalInformation() {
        return personalInformation;
    }

    public void setPersonalInformation(Map<String, String> personalInformation) {
        this.personalInformation = personalInformation;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("\n----- Employee Information-----\n");
        sb.append("ID: " + getId() + "\n");
        sb.append("Name: " + getName() + "\n");
        sb.append("Age: " + getAge() + "\n");
        sb.append("Salary: $" + getSalary() + "\n");
        sb.append("Designation: " + getDesignation() + "\n");
        sb.append("Phone Numbers: " + Arrays.toString(getPhoneNumbers()) + "\n");
        sb.append("Address: " + getAddress() + "\n");
        sb.append("Personal Information:" + getPersonalInformation() + "\n");
        sb.append("*****************************");
        return sb.toString();
    }
}

With the POJOs ready to be populated with JSON data, lets use ObjectMapper of Jackson to perform the binding.

ObjectMapperDemo.java

   //package guru.springframework.blog.jsonwithjackson.jsonreader;

import com.fasterxml.jackson.databind.ObjectMapper;
import guru.springframework.blog.jsonwithjackson.domain.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;

public class ObjectMapperDemo {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    public Employee readJsonWithObjectMapper() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        Employee emp = objectMapper.readValue(new File("employee.json"), Employee.class);
        logger.info(emp.toString());
        return emp;
    }

}

In the ObjectMapperDemo class above, we created an ObjectMapper object and called its overloaded readValue() method passing two parameters. We passed a File object representing the JSON file as the first parameter, and Employee.class as the target to map the JSON values as the second parameter. The readValue() method returns an Employee object populated with the data read from the JSON file.

The test class for ObjectMapperDemo is this.

ObjectMapperDemoTest.java

   //package guru.springframework.blog.jsonwithjackson.jsonreader;

import org.junit.Test;

import static org.junit.Assert.*;

public class ObjectMapperToMapDemoTest {

    @Test
    public void testReadJsonWithObjectMapper() throws Exception {
        ObjectMapperToMapDemo obj= new ObjectMapperToMapDemo();
        obj.readJsonWithObjectMapper();
    }
}

The output on running the test is this.

Output of ObjectMapperDemo

Simple Data Binding in Jackson

In the example above, we covered full data binding. It is a variant of Jackson data binding that reads JSON into application-specific JavaBeans types.

The other type is simple data binding where you read JSON into built-in Java types( such as Map and List) and also wrapper types (such as String, Boolean, and Number).

In this example of simple data binding, let’s bind the data of employee.json to a generic Map.

ObjectMapperToMapDemo.java

   //package guru.springframework.blog.jsonwithjackson.jsonreader;


import com.fasterxml.jackson.databind.ObjectMapper;
import guru.springframework.blog.jsonwithjackson.domain.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map;

public class ObjectMapperToMapDemo {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    public void readJsonWithObjectMapper() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        Map entry : empMap.entrySet())
        {
            logger.info("\n----------------------------\n"+entry.getKey() + "=" + entry.getValue()+"\n");
        }
    }
}

In the ObjectMapperToMapDemo class above, notice the overloaded readValue() method where we used a FileInputStream to read employee.json. Other overloaded versions of this method allow you to read JSON from String, Reader, URL, and byte array. Once ObjectMapper maps the JSON data to the declared Map, we iterated over and logged the map entries.

The test class for the ObjectMapperToMapDemo class is this.

ObjectMapperToMapDemoTest.java

   //package guru.springframework.blog.jsonwithjackson.jsonreader;

import org.junit.Test;

import static org.junit.Assert.*;

public class ObjectMapperToMapDemoTest {

    @Test
    public void testReadJsonWithObjectMapper() throws Exception {
        ObjectMapperToMapDemo obj= new ObjectMapperToMapDemo();
        obj.readJsonWithObjectMapper();
    }
}

The output on running the test is this.
Output of ObjectMapperToMapDemo

With simple data binding, we don’t require writing JavaBeans with properties corresponding to the JSON data. This is particularly useful in situations where we don’t know about the JSON data to process. In such situations, another approach is to use the JSON Tree Model. That I will discuss next.

Reading JSON into a Tree Model

In the JSON Tree Model, the ObjectMapper constructs a hierarchical tree of nodes from JSON data. If you are familiar with XML processing, you can relate the JSON Tree Model with XML DOM Model. In the JSON Tree Model, each node in the tree is of the JsonNode type, and represents a piece of JSON data. In the Tree Model, you can randomly access nodes with the different methods that JsonNode provides.

The code to generate a Tree Model of the employee.json file is this.

. . .
public class JsonNodeDemo {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    JsonNode rootNode;
    ObjectMapper objectMapper;
    public JsonNodeDemo()throws IOException{
        objectMapper = new ObjectMapper();
        rootNode = objectMapper.readTree(new File("employee.json"));
    }
public JsonNode readJsonWithJsonNode() throws JsonProcessingException {
    String prettyPrintEmployee = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
    logger.info(prettyPrintEmployee+"\n");
    return rootNode;
}

. . .	 
}

In the constructor of the JsonNodeDemo class above, we created an ObjectMapper instance. We called its readTree() method passing a File object representing the JSON document as parameter. The readTree() method returns a JsonNode object that represents the hierarchical tree of employee.json. In the readJsonWithJsonNode() method, we used the ObjectMapper to write the hierarchical tree to a string using the default pretty printer for indentation.

The output on running the code is this.

{
  "id" : 123,
  "name" : "Henry Smith",
  "age" : 28,
  "salary" : 2000,
  "designation" : "Programmer",
  "address" : {
    "street" : "Park Avn.",
    "city" : "Westchester",
    "zipcode" : 10583
  },
  "phoneNumbers" : [ 654321, 222333 ],
  "personalInformation" : {
    "gender" : "Male",
    "maritialstatus" : "Married"
  }
}

Next, let’s access the value of the name node with this code.

. . .
public String readNameNode()
{
    JsonNode nameNode=rootNode.path("name");
    String name=nameNode.asText();
    logger.info("\n----------------------------\nEmployee Nme: "+name+"\n");
    return name;
}
. . .

In the code above, we called the path() method on the JsonNode object that represents the root node. To the path() method, we passed the name of the node to access, which in this example is name. We then called the asText() method on the JsonNode object that the path() method returns. The asText() method that we called returns the value of the name node as a string.

The output of this code is:

----------------------------
Employee Name: Henry Smith

Next, let’s access the personalInformation and phoneNumbers nodes.

. . .
public Map<String,String> readPersonalInformation() throws JsonProcessingException
 {
     JsonNode personalInformationNode = rootNode.get("personalInformation");
     Map<String, String> personalInformationMap = objectMapper.convertValue(personalInformationNode, Map.class);
     for (Map.Entry<String, String> entry : personalInformationMap.entrySet())
     {
         logger.info("\n----------------------------\n"+entry.getKey() + "=" + entry.getValue()+"\n");
     }
       return personalInformationMap;
 }

 public Iterator<JsonNode> readPhoneNumbers(){
     JsonNode phoneNumbersNode = rootNode.path("phoneNumbers");
     Iterator<JsonNode> elements = phoneNumbersNode.elements();
     while(elements.hasNext()){
         JsonNode phoneNode = elements.next();
         logger.info("\n----------------------------\nPhone Numbers = "+phoneNode.asLong());
     }
     return elements;
 }
. . . .

Few key things to note in the code above. In Line 4, notice that we called the get() method instead of path() on the root node. Both the methods perform the same functions – they return the specified node as a JsonNode object. The difference is how they behave when the specified node is not present, or the node doesn’t have an associated value.

When the node is not present or does not have a value, the get() method returns a null value, while the path() method returns a JsonNode object that represents a “missing node“. The “missing node” returns true for a call to the isMissingNode() method. The remaining code from Line 5- Line 9 is simple data binding, where we mapped the personalInformation node to a Map<String, String> object.

In the readPhoneNumbers() method, we accessed the phoneNumbers node. Note that in employee.json, phoneNumbers is represented as a JSON array (Enclosed within [] brackets). After mapping, we accessed the array elements with a call to the elements() method in Line 15. The elements() method returns an Iterator of JsonNode that we traversed and logged the values.

The output on running the code is this.

----------------------------
gender=Male
----------------------------
maritialstatus=Married

----------------------------
Phone Numbers = 654321
----------------------------
Phone Numbers = 222333

The complete code of generating the JSON tree model and accessing its nodes is this.

JsonNodeDemo.java

   //package guru.springframework.blog.jsonwithjackson.jsonreader;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

public class JsonNodeDemo {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    JsonNode rootNode;
    ObjectMapper objectMapper;
    public JsonNodeDemo()throws IOException{
        objectMapper = new ObjectMapper();
        rootNode = objectMapper.readTree(new File("employee.json"));
    }
        public JsonNode readJsonWithJsonNode() throws JsonProcessingException {
        String prettyPrintEmployee = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
        logger.info(prettyPrintEmployee+"\n");
        return rootNode;
    }
        public String readNameNode()
        {
            JsonNode nameNode=rootNode.path("name");
            String name=nameNode.asText();
            logger.info("\n----------------------------\nEmployee Name: "+name+"\n");
            return name;
        }
      public Map<String,String> readPersonalInformation() throws JsonProcessingException
       {
           JsonNode personalInformationNode = rootNode.get("personalInformation");
           Map<String, String> personalInformationMap = objectMapper.convertValue(personalInformationNode, Map.class);
           for (Map.Entry<String, String> entry : personalInformationMap.entrySet())
           {
               logger.info("\n----------------------------\n"+entry.getKey() + "=" + entry.getValue()+"\n");
           }
             return personalInformationMap;
       }

       public Iterator readPhoneNumbers(){
           JsonNode phoneNumbersNode = rootNode.path("phoneNumbers");
           Iterator elements = phoneNumbersNode.elements();
           while(elements.hasNext()){
               JsonNode phoneNode = elements.next();
               logger.info("\n----------------------------\nPhone Numbers = "+phoneNode.asLong());
           }
           return elements;
       }
}


Writing JSON using Jackson

JSON data binding is not only about reading JSON into Java objects. With the ObjectMapper of JSON data binding, you can also write the state of Java objects to a JSON string or a JSON file.

Let’s write a class that uses ObjectMapper to write an Employee object to a JSON string and a JSON file.

JsonWriterObjectMapper.java

   //package guru.springframework.blog.jsonwithjackson.jsonwriter;


import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.SerializationFeature;
import guru.springframework.blog.jsonwithjackson.domain.Employee;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;


public class JsonWriterObjectMapper {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    ObjectMapper objectMapper = new ObjectMapper();

    public void writeEmployeeToJson(Employee emp) {
        try {
            String jsonInString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(emp);
            logger.info("Employee JSON is\n" + jsonInString);
            objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
            objectMapper.writeValue(new File(emp.getId()+"_employee.json"), emp);

        } catch (JsonGenerationException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

In Line 22 of the code above, we used an ObjectMapper object to write an Employee object to a JSON string using the default pretty printer for indentation.

In Line 24, we called the configure() method to configure ObjectMapper to indent the JSON output.

In Line 25, we called the overloaded writeValue() method to write the Employee object to the file provided as the first parameter. The other overloaded writeValue() methods allow you to write JSON output using OutputStream and Writer.

The test code for the JsonWriterObjectMapper class is this.

JsonWriterObjectMapperTest.java

   //package guru.springframework.blog.jsonwithjackson.jsonwriter;

import guru.springframework.blog.jsonwithjackson.domain.Address;
import guru.springframework.blog.jsonwithjackson.domain.Employee;
import org.junit.Before;
import org.junit.Test;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.*;


public class JsonWriterObjectMapperTest {
    Employee emp=new Employee();
    @Before
    public void setUpEmployee() throws Exception {
      Address address=new Address();
      address.setStreet("Lake Park Road");
      address.setCity("Phoenix");
      address.setZipcode(85003);
      emp.setId(124);
      emp.setName("Alice Celci");
      emp.setAge(24);
      emp.setSalary(new BigDecimal(1800));
      emp.setDesignation("UI Designer");
      emp.setAddress(address);
      emp.setPhoneNumbers(new long[]{246802});
      Map<String, String> infoMap = new HashMap<>();
      infoMap.put("gender", "Female");
      infoMap.put("maritialstatus", "Unmarried");
      emp.setPersonalInformation(infoMap);
    }

    @Test
    public void testWriteEmployeeToJson() throws Exception {
        JsonWriterObjectMapper jsonWriter=new JsonWriterObjectMapper();
        jsonWriter.writeEmployeeToJson(emp);
    }


}

In the test class above, we used the JUnit @Before annotation on the setUpEmployee() method to initialize the Address and Employee classes. If you are new to JUnit, checkout my series on JUnit starting from here. In the @Test annotated method, we called the writeEmployeeToJson() method of JsonWriterObjectMapper, passing the initialied Employee object.

The output on running the test is this.
Output of JsonWriterObjectMapper

Spring Support for Jackson

Spring support for Jackson has been improved lately to be more flexible and powerful. If you are developing Spring Restful webservice using Spring RestTemplate API, you can utilize Spring Jackson JSON API integration to send back JSON response. In addition, Spring MVC now has built-in support for Jackson’s Serialization Views. Jackson provides first class support for some other data formats than JSON- Spring Framework and Spring Boot provide built-in support Jackson based XML.

In future posts, I will discuss more about advanced JSON-based processing with Jackson- particularly Jackson Streaming Model for JSON, and also Jackson based XML processing.

Conclusion

Jackson is one of the several available libraries for processing JSON. Some others are Boon, GSON, and Java API for JSON Processing.

One advantage that Jackson has over other libraries is its maturity. Jackson has evolved enough to become the preferred JSON processing library of some major web services frameworks, such as Jersey, and RESTEasy. Open source enterprise projects, such as Hadoop and Camel also use Jackson for handling data definition in enterprise integration.

About jt

    You May Also Like

    7 comments on “How to Process JSON with Jackson

    1. May 21, 2016 at 9:38 am

      Great article John!

      Reply
      • May 21, 2016 at 5:16 pm

        Thanks Dan!

        Reply
    2. May 24, 2016 at 12:08 pm

      Great article! Thanks.

      Reply
    3. August 31, 2016 at 9:38 pm

      Great article. Thank you. Where can I download the code for this?

      Reply
    4. September 1, 2016 at 7:55 am

      Checkout out the source code on this branch – https://github.com/springframeworkguru/blogposts/tree/jackson-json

      Reply
    5. April 22, 2018 at 2:51 pm

      If I send a Json with null fields, how to avoid that. I see @JsonInclude(Inculde.NON_NULL) but Spring Boot reads null fields and that make part of structure of the object readed.

      Reply
    6. May 10, 2020 at 6:46 am

      Can you share example of JSON procesing which is deeply nested.i e node within node upto 4 to 5 levels

      Reply

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.