Processing JSON with Jackson
7 CommentsIt’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.
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 MappersonalInformation; /*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 getPersonalInformation() { return personalInformation; } public void setPersonalInformation(Map 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.
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 empMap = objectMapper.readValue(new FileInputStream("employee.json"),Map.class); for (Map.Entry 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.
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 MapreadPersonalInformation() throws JsonProcessingException { JsonNode personalInformationNode = rootNode.get("personalInformation"); Map personalInformationMap = objectMapper.convertValue(personalInformationNode, Map.class); for (Map.Entry 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; } }
The test class for the JsonNodeDemo
class above is this.
JsonNodeDemoTest.java
//404: Not Found
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}); MapinfoMap = 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.
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, RESTEasy, Restlet, and Apache Wink. Open source enterprise projects, such as Hadoop and Camel also use Jackson for handling data definition in enterprise integration.
Dan Vega (@therealdanvega)
Great article John!
jt
Thanks Dan!
Hugo Marques
Great article! Thanks.
Shankar P S
Great article. Thank you. Where can I download the code for this?
http://springframework.guru
Checkout out the source code on this branch – https://github.com/springframeworkguru/blogposts/tree/jackson-json
Fernando
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.
Sunil Kore
Can you share example of JSON procesing which is deeply nested.i e node within node upto 4 to 5 levels