Using the Jackson API for processing JSON

 

Using the Jackson API for processing JSON

Jackson is a multipurpose data processing Java library. The primary capability of this tool is the support for processing JSON. It also has additional modules for processing the data encoded in other popular formats, such as Apache Avro (a data serialization system), Concise Binary Object Representation (CBOR), a binary JSON format, Smile (a binary JSON format), XML, comma-separated values(CSV), and YAML. In this section, you will learn how to use Jackson APIs to process JSON.

Jackson provides the following three methods for processing JSON:

  • Tree model APIs: This method provides APIs for building a tree representation of a JSON document
  • Data binding API: This method provides APIs for converting a JSON document into and from Java objects
  • Streaming API: This method provides streaming APIs for reading and writing a JSON document

We will discuss the preceding three methods in detail in this section.


Processing JSON with Jackson tree model APIs

The Jackson tree model API provides an in-memory representation of the JSON data. A client can optionally modify the object model representation. Here is a list of the core classes that you may need to know while dealing with tree model APIs in Jackson:

Class

Description

com.fasterxml.jackson.databind.ObjectMapper

This mapper class provides the functionality for converting between Java objects and matching JSON representations.

com.fasterxml.jackson.databind.JsonNode

This class is the base class for all JSON nodes, which form the basis of the JSON tree model in Jackson.

Using Jackson tree model APIs to query and update data

You will use the com.fasterxml.jackson.databind.ObjectMapper class to convert the JSON data into a tree representation. This class has a variety of APIs to build a tree from the JSON data. The tree representation built from JSON is made up of com.fasterxml.jackson.databind.JsonNode instances. This is similar to the DOM nodes in an XML DOM tree. You can also navigate through JsonNode to identify specific elements present in the tree hierarchy. The following example illustrates the use of Jackson tree model APIs.

This example generates a tree hierarchy for the JSON array of employee objects and then queries the generated tree for the employee nodes with the null email value. This example updates all the null email values with the system-generated email addresses for later processing.

The readTree(InputStream in) instance defined on the com.fasterxml.jackson.databind.ObjectMapperclass helps you to deserialize the JSON content as a tree (expressed using a set of JsonNode instances). To query JsonNode for a specific field name, you can call path(String fieldName) on the underlying JsonNode instance. The following code snippet will help you understand the use of these APIs:

//Other imports are removed for brevity 
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

// Read in the JSON employee array form emp-array.json file
InputStream inputStream =
getClass().getResourceAsStream("/emp-array.json");
//Create ObjectMapper instance
//ObjectMapper provides functionality for creating tree
//structure for JSON content
ObjectMapper objectMapper = new ObjectMapper();

//Read JSON content in to tree
JsonNode rootNode = objectMapper.readTree(inputStream);

//Check if the json content is in array form
if (rootNode.isArray()) {
//Iterate over each element in the array
for (JsonNode objNode : rootNode) {
//Find out the email node by traversing tree
JsonNode emailNode = objNode.path("email");
//if email is null, then update with
//a system generated email
if(emailNode.textValue() == null ){
String generatedEmail=getSystemGeneratedEmail();
((ObjectNode)objNode).put("email", generatedEmail );
}
}
}
//Write the modified tree to a json file
objectMapper.writeValue(new File("emp-modified-array.json"),
rootNode);
if(inputStream != null)
inputStream.close();


The tree model API discussed in this section produces a generalized tree representation for the JSON content. To learn how to generate a more specific object representation for JSON, refer to the next section.


Processing JSON with Jackson data binding APIs

Jackson data binding is used to convert the JSON representation into and from Plain Old Java Object (POJO) by using property accessors or annotations. With this API, you can either generate generic collection classes or more specific Java objects, such as the Employee object, for representing JSON data. Let's take a quick look at these two variants available for representing the JSON data.


Simple Jackson data binding with generalized objects

Sometimes, you may need to deal with highly dynamic JSON content where you may not be able to map data to a specific Java object, as the structure of the data changes dynamically. In such a scenario, you can use the simple binding APIs offered by the Jackson framework. You will use the Java maps, lists, strings, numbers, Booleans, and nulls for representing dynamic JSON data.

The following code snippet converts a JSON string into a map object. The com.fasterxml.jackson.core.type.TypeReference class is used for passing a generic type definition, which defines a type to bind to, as follows:

String jsonString =  
"{\" firstName\":\"John\",\"lastName\":\"Chen\"}";
ObjectMapper objectMapper = new ObjectMapper();
//properties will store name and value pairs read from jsonString
Map<String, String> properties = objectMapper.readValue(
jsonString, new TypeReference<Map<String, String>>() { });

Full Jackson data binding with specialized objects

If the JSON data format, which a client receives, is well-structured, you can directly map the content to a concrete Java class. A full data binding solution fits well in such a scenario. For instance, you can create an Employee class for representing the employee data presented in the JSON format as long as the JSON content structure does not change. The following example uses the data binding offering from Jackson to convert the JSON content from the emp.json file into an Employee object. This example calls readValue(InputStream src, Class<T> valueType) on ObjectMapper to get the Java representation for the JSON content:

// emp.json file has following contents: 
//{"employeeId":100,"firstName":"John","lastName":"Chen"}
ObjectMapper objectMapper = new ObjectMapper();
Employee employee = objectMapper.readValue(new File("emp.json"),
Employee.class);

The next example demonstrates how you can create a Java collection containing the Employee objects from a JSON array of employees. Here, we need to construct a Java collection type, indicating the type of elements in the collection. To create a collection type, you can call the constructCollectionType() method on the com.fasterxml.jackson.databind.type.TypeFactory instance returned by ObjectMapper:

ObjectMapper objectMapper = new ObjectMapper();  
CollectionType collectionType =
objectMapper.getTypeFactory().constructCollectionType
(List.class, Employee.class);
//"emp-array.json" file contains JSON array of employee data
List<Employee> emp = objectMapper.readValue(new File
("emp-array.json"), collectionType);

To convert a Java object into the JSON representation, you can call the writeValue(OutputStream out, Object value) method on ObjectMapper. The writeValue() method serializes any Java value to JSON and writes it to the output stream present in the method call. The following code snippet converts the employee object into the JSON structure and writes the content to the emp.json file:

//Get the employee object 
Employee employee = getEmployeeEntity();
//Convert the object in to JSON and write to a file
objectMapper.writeValue(new File("emp.json"), employee);
How does Jackson map JSON object values to a Java class?

The default mapping mechanism used by Jackson is based on the bean naming properties. The binding layer copies the matching properties from the source to the destination. This implies that all the names present in a JSON object need to match the Java class properties for the default mapping mechanism to work. However, you can override the default mapping behavior by annotating a desired field (or by the getter and setter methods) with @JsonProperty. This annotation is used to override the default property name that is used during the serialization and deserialization process. To learn more, visit http://wiki.fasterxml.com/JacksonAnnotations.


Processing JSON with Jackson streaming APIs

The Jackson framework supports the streaming API for reading and writing JSON contents. You will use org.codehaus.jackson.JsonParser to read the JSON data and org.codehaus.jackson.JsonGenerator to write the data. The following table lists the important classes in the streaming model API:

Class

Description

com.fasterxml.jackson.core.JsonParser

This class is used for reading the JSON content.

com.fasterxml.jackson.core.JsonGenerator

This class is used for writing the JSON content.

com.fasterxml.jackson.core.JsonFactory

This is the main factory class of the Jackson package. It is used to generate JsonParser and JsonWriter.



Using Jackson streaming APIs to parse JSON data

The following example illustrates Jackson streaming APIs for reading JSON data. This example uses streaming APIs to generate a Java model for the JSON array of employee objects. As in the earlier examples, the emp-array.json file is used as the input source. The steps are as follows:

  1. Create the com.fasterxml.jackson.core.JsonParser instance by using com.fasterxml.jackson.core.JsonFactory. The JsonParser class reads the JSON content from the file input stream.
  2. The next step is to start parsing the JSON content read from the input source. The client may call the nextToken() method to forward the stream enough to determine the type of the next token. Based on the token type, the client can take an appropriate action. In the following sample code, the client checks for the start of an object (JsonToken.START_OBJECT) in order to copy the current JSON object to a new Employee instance. We use ObjectMapper to copy the content of the current JSON object to the Employee class:
//Step 1: Finds a resource with a given name. 
InputStream inputStream = getClass().getResourceAsStream(
"/emp-array.json");
//Creates Streaming parser
JsonParser jsonParser = new
JsonFactory().createParser(inputStream);

//Step 2: Start parsing the contents
//We will use data binding feature from ObjectMapper
//for populating employee object
ObjectMapper objectMapper = new ObjectMapper();
//Continue the parsing till stream is opened or
//no more token is available
while (!jsonParser.isClosed()) {
JsonToken jsonToken = jsonParser.nextToken();
// if it is the last token then break the loop
if (jsonToken == null) {
break;
}
//If this is start of the object, then create
//Employee instance and add it to the result list
if (jsonToken.equals(JsonToken.START_OBJECT)) {
//Use the objectMapper to copy the current
// JSON object to Employee object
employee = objectMapper.readValue(jsonParser,
Employee.class);
//Add the newly copied instance to the list
employeeList.add(employee);

}

}
//Close the stream after the use to release the resources
if (inputStream != null) {
inputStream.close();
}
if (jsonParser != null) {
jsonParser.close();
}

Using Jackson streaming APIs to generate JSON

The following example illustrates Jackson streaming APIs for writing JSON data. This example reads a list of Employee objects, converts them into JSON representations, and then writes to the OutputStream object. The steps are as follows:

  1. The following code snippet generates com.fasterxml.jackson.core.JsonGenerator by using com.fasterxml.jackson.core.JsonFactory:
OutputStream outputStream = new 
FileOutputStream("emp-array.json");JsonGenerator jsonGenerator = new
JsonFactory().createGenerator(outputStream,
JsonEncoding.UTF8);
  1. The next step is to build the JSON representation for the list of employees. The writeStartArray() method writes the starting marker for the JSON array ([). Now, write the marker for the object ({) by calling writeStartObject(). This is followed by the name-value pairs for the object by calling an appropriate write method. To write the end marker for the object (}), call writeEndObject(). Finally, to finish writing an array, call writeEndArray():
jsonGenerator.writeStartArray(); 
List<Employee> employees = getEmployeesList();
for (Employee employee : employees) {
jsonGenerator.writeStartObject();
jsonGenerator.writeNumberField("employeeId",
employee.getEmployeeId()); jsonGenerator.writeStringField("firstName",
employee.getFirstName()); jsonGenerator.writeStringField("lastName",
employee.getLastName());
jsonGenerator.writeEndObject();

}
//JsonGenerator class writes the JSON content to the
//specified OutputStream.

jsonGenerator.writeEndArray();
  1. Close the stream object after use:
//Close the streams to release associated resources 
jsonGenerator.close();
outputStream.close();
With this topic, we are ending our discussion on Jackson. In this section, we discussed the various categories of APIs for processing JSON. An in-depth coverage of the Jackson API is beyond the scope of this book. More details on Jackson are available at https://github.com/FasterXML/Jackson.

The link that answers some of the generic queries that you may have around Jackson is https://github.com/FasterXML/jackson/wiki/FAQ.

In the next section, we will discuss Gson, yet another framework for processing JSON. 


Comments

Popular posts from this blog

Understanding the JAX-RS resource life cycle

Generating a chunked output using Jersey APIs

Jersey client API for reading chunked input