Processing JSON With Jackson
JSON 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.
Join the DZone community and get the full member experience.
Join For FreeIt'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. XML provides a very structured way of sharing data between systems. XML is so structured, many find it too restrictive. JSON is a popular alternative to XML. JSON offers a lighter and more forgiving syntax than XML.
JSON 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. JSON 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 is 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 a 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, thus overriding the Spring Boot curated dependency versions you may encounter version conflicts.
The proper way for Jackson dependency declaration is to use the Spring Boot curated dependency by not including the version tag on the main Jackson library, like this.
. . .
<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, let’s 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
The example above we covered full data binding – 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.
An example of simple data binding is to bind the data of employee.json to a generic Map is this.
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 and 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.
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<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;
}
}
The test class for the JsonNodeDemo class above is this.
JsonNodeDemoTest.java
package guru.springframework.blog.jsonwithjackson.jsonreader;
import com.fasterxml.jackson.databind.JsonNode;
import guru.springframework.blog.jsonwithjackson.domain.Address;
import org.junit.Test;
import java.util.Iterator;
import java.util.Map;
import static org.junit.Assert.*;
public class JsonNodeDemoTest {
@Test
public void testReadJsonWithJsonNode() throws Exception {
JsonNodeDemo jsonNodeDemo=new JsonNodeDemo();
JsonNode rootNode=jsonNodeDemo.readJsonWithJsonNode();
assertNotNull(rootNode);
String name=jsonNodeDemo.readNameNode();
assertEquals("Henry Smith", name);
Map<String, String> addressMap=jsonNodeDemo.readPersonalInformation();
assertEquals(2, addressMap.size());
Iterator<JsonNode> elements=jsonNodeDemo.readPhoneNumbers();
assertNotNull(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.
Spring Support for Jackson
Spring support for Jackson has been improved lately to be more flexible and powerful. If you are developing Spring Restful web service 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. Also, 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.
Published at DZone with permission of John Thompson, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments