Intro to JSON Parsing with Jackson ObjectMapper - Jackson 2.X

Breaking

Wednesday, December 26, 2018

Intro to JSON Parsing with Jackson ObjectMapper - Jackson 2.X

Jackson json Objectmapper

1. Overview

This content is focused on understanding the Jackson JSON Parser ObjectMapper class – and how to serialize Java objects into JSON and deserialize JSON string into Java objects. To get a more vivid understanding about Jackson library in general, this Jackson Tutorial is a good place to start, let's dive in to the world of Object Parsing with Jackson.

2. Add Dependencies

There are two major ways amongst other to add dependencies in any java project, we will be sticking to gradle and maven.
Let’s first add the following dependencies to pom.xml:


Maven:
1
2
3
4
5
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

Gradle:
1
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'

This dependency will transitively add the following libraries to the classpath of your java project:
  1. jackson-annotations-2.9.8.jar
  2. jackson-core-2.9.8.jar
  3. jackson-databind-2.9.8.jar
Be certain to always use the latest versions as seen the mvnrepository for Jackson databind or any other dependency you may need

3. Reading and Writing Using ObjectMapper

Codes don't lie let’s start with the basic read and write operations.
The simple readValue API of the ObjectMapper is a good entry-point; this can be used to parse or deserialize JSON content into a Java object.
On the write side of things – the writeValue API can be used to serialize any Java object to a JSON output.
We’ll use the following Car class with two fields as the object to serialize or deserialize throughout this article:
1
2
3
4
5
public class Car {
    private String color;
    private String type;
    // standard getters setters
}

3.1. Java Object to JSON

A simple but would suffice example of serializing Java Object into a JSON object using the writeValue method of ObjectMapper class:
1
2
3
ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("yellow", "renault");
objectMapper.writeValue(new File("path/to/car.json"), car);
The output of the above can be seen in the file as follows:
1
{"color":"red","type":"Toyota"}
The methods writeValueAsString and writeValueAsBytes of ObjectMapper class generates a JSON from a Java object and returns the generated JSON object as a string or as a byte array:
1
String carAsString = objectMapper.writeValueAsString(car);

3.2. JSON to Java Object

Below is a simple example of JSON string converted to Java object using the ObjectMapper class:
1
2
String carJson = "{ \"color\" : \"gray\", \"type\" : \"Audi\" }";
Car car = objectMapper.readValue(carJson, Car.class); 
The readValue method/function accepts the JSON string and the class to deserialize to, but also accepts other forms of arguments like a file containing JSON string:
1
Car car = objectMapper.readValue(new File("path/to/json_car.json"), Car.class);
or a URL:
1
Car car = objectMapper.readValue(new URL("url/to/json_car.json"), Car.class);

3.3. JSON to Jackson JsonNode

Alternatively, a JSON Object or string can be parsed into a JsonNode object which is a class from  and used to retrieve data from a given specified node:
1
2
3
4
String carJson = "{ \"color\" : \"Blue\", \"type\" : \"Bentley\" }";
JsonNode jsonNode = objectMapper.readTree(carJson);
String color = jsonNode.get("color").asText();
// Output: color -> Blue

3.4. Creating Java List of Objects from JSON Array String

A JSON in the form of an array can be parsed into a Java object list by parsing in the following way:
1
2
3
String jsonCarArray =
  "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});

3.5. Creating Java Map from JSON String

A JSON in the form of string can be parsed into a Java Map object by parsing in the following way:
1
2
3
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Map<String, Object> map
  = objectMapper.readValue(json, new TypeReference<Map<String,Object>>(){});

4. Advanced Features

Jackson has some great strength and features, some of which  is the highly customizable serialization and deserialization process.
In this section, we will go through some advanced features where the input or the output JSON response can be different from the object which generates or consumes the response.

4.1. Configuring Serialization or Deserialization Feature

While converting JSON objects to Java classes, in case the JSON string has some new fields, then the default process will run in an exception:
1
2
String jsonString
  = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" : \"1970\" }";
The JSON string in the above example in the default parsing process to the Java object for the Class Carwill result in the UnrecognizedPropertyException exception.
Through the configure method we can extend the default process to ignore the new fields:
1
2
3
4
5
6
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Car car = objectMapper.readValue(jsonString, Car.class);
JsonNode jsonNodeRoot = objectMapper.readTree(jsonString);
JsonNode jsonNodeYear = jsonNodeRoot.get("year");
String year = jsonNodeYear.asText();
Yet another option is based on the FAIL_ON_NULL_FOR_PRIMITIVES which defines if the null values for primitive values are allowed:
1
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
Similarly, FAIL_ON_NUMBERS_FOR_ENUM controls if enum values are allowed to be serialized/deserialized as numbers
1
objectMapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false);
You can find the comprehensive list of serialization and deserialization features on the official site.

4.2. Creating Custom Serializer or Deserializer

Another essential feature of the ObjectMapper class is the ability to register custom serializer and deserializer. Custom serializer and deserializer are very useful in situations where the input or the output JSON response is different in structure than the Java class into which it must be serialized or deserialized.
Below is an example of custom JSON serializer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CustomCarSerializer extends StdSerializer<Car> {
     
    public CustomCarSerializer() {
        this(null);
    }
    public CustomCarSerializer(Class<Car> t) {
        super(t);
    }
    @Override
    public void serialize(
      Car car, JsonGenerator jsonGenerator, SerializerProvider serializer) {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("car_brand", car.getType());
        jsonGenerator.writeEndObject();
    }
}
This custom serializer can be invoked like this:
1
2
3
4
5
6
7
ObjectMapper mapper = new ObjectMapper();
SimpleModule module =
  new SimpleModule("CustomCarSerializer", new Version(1, 0, 0, null, null, null));
module.addSerializer(Car.class, new CustomCarSerializer());
mapper.registerModule(module);
Car car = new Car("yellow", "renault");
String carJson = mapper.writeValueAsString(car);
Here’s what the Car looks like (as JSON output) on the client side:
1
var carJson = {"car_brand":"renault"}
And here’s an example of a custom JSON deserializer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CustomCarDeserializer extends StdDeserializer<Car> {
     
    public CustomCarDeserializer() {
        this(null);
    }
    public CustomCarDeserializer(Class<?> vc) {
        super(vc);
    }
    @Override
    public Car deserialize(JsonParser parser, DeserializationContext deserializer) {
        Car car = new Car();
        ObjectCodec codec = parser.getCodec();
        JsonNode node = codec.readTree(parser);
         
        // try catch block
        JsonNode colorNode = node.get("color");
        String color = colorNode.asText();
        car.setColor(color);
        return car;
    }
}
This custom deserializer can be invoked in the following way:
1
2
3
4
5
6
7
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
ObjectMapper mapper = new ObjectMapper();
SimpleModule module =
  new SimpleModule("CustomCarDeserializer", new Version(1, 0, 0, null, null, null));
module.addDeserializer(Car.class, new CustomCarDeserializer());
mapper.registerModule(module);
Car car = mapper.readValue(json, Car.class);

4.3. Handling Date Formats

The default serialization of java.util.Date produces a number i.e. epoch timestamp (number of milliseconds since January 1st, 1970, UTC). But this is not very human readable, and further conversion is needed to display human readable format.
Let us wrap the Car instance we used so far inside the Request class with the datePurchased property:
1
2
3
4
5
6
7
public class Request
{
    private Car car;
    private Date datePurchased;
    // standard getters setters
}
To control the String format of a date, and set it to e.g. yyyy-MM-dd HH:mm a z, consider the following snippet:
1
2
3
4
5
ObjectMapper objectMapper = new ObjectMapper();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
objectMapper.setDateFormat(df);
String carAsString = objectMapper.writeValueAsString(request);
// output: {"car":{"color":"yellow","type":"renault"},"datePurchased":"2016-07-03 11:43 AM CEST"}
To learn more about serializing dates with Jackson, read our more in-depth write-up.

4.4. Handling Collections

Another small but useful feature available through DeserializationFeature is the ability to generate the type of collection desired from a JSON Array response, e.g. as an array:
1
2
3
4
5
6
String jsonCarArray =
  "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
Car[] cars = objectMapper.readValue(jsonCarArray, Car[].class);
// print cars
Or as a List:
1
2
3
4
5
String jsonCarArray =
  "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";
ObjectMapper objectMapper = new ObjectMapper();
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});
// print cars
More about handling collections with Jackson is available here.

5. Conclusion

Jackson ObjectMapper is a solid and mature JSON serialization/deserialization library for Java. And the ObjectMapper API provides a straightforward way to parse and generate JSON response objects with a lot of flexibility and dynamic features.
The article discusses the main features which make the library so popular. The source code that accompanies the article can be found over on GitHub feel free to clone and edit as your needs require.

No comments:

Post a Comment