I just released Module 10 in the Master Class of “REST with Spring”:

>> THE "REST WITH SPRING" CLASSES

1. Overview

This write-up is focused on understanding the Jackson ObjectMapper class – and how to serialize Java objects into JSON and deserialize JSON string into Java objects. To understand more about Jackson library in general, the Jackson Tutorial is a good place to start.

2. Dependencies

Let’s first add the following dependencies to pom.xml:

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

This dependency will transitively add the following libraries to the classpath:

  1. jackson-annotations-2.7.5.jar
  2. jackson-core-2.7.5.jar
  3. jackson-databind-2.7.5.jar

Always use the latest versions as seen the maven central repository for Jackson databind.

3. Reading and Writing Using ObjectMapper

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 as JSON output.

We’ll use the following Car class with two fields as the object to serialize or deserialize throughout this article:

public class Car {

    private String color;
    private String type;

    // standard getters setters
}

3.1. Java Object to JSON

A simple example of serialization of Java Object into JSON using the writeValue method of ObjectMapper class:

ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("yellow", "renault");
objectMapper.writeValue(new File("target/car.json"), car);

The output of the above can be seen in the file as follows:

{"color":"yellow","type":"renault"}

The methods writeValueAsString and writeValueAsBytes of ObjectMapper class generates a JSON from a Java object and returns the generated JSON as a string or as a byte array:

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:

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Car car = objectMapper.readValue(json, Car.class);	

The readValue function also accepts other forms of input like a file containing JSON string:

Car car = objectMapper.readValue(new File("target/json_car.json"), Car.class);

or a URL:

Car car = objectMapper.readValue(new URL("target/json_car.json"), Car.class);

3.3. JSON to Jackson JsonNode

Alternatively, a JSON can be parsed into a JsonNode object and used to retrieve data from a given specific node:

String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }";
JsonNode jsonNode = objectMapper.readTree(json);
String color = jsonNode.get("color").asText();
// Output: color -> Black

3.4. Creating Java List 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:

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:

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Map<String, Object> map = objectMapper.readValue(json, new TypeReference<Map<String,Object>>(){});

4. Advanced Features

One of the greatest strength of the Jackson library 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:

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 Car will result in the UnrecognizedPropertyException exception.

Through the configure method we can extend the default process to ignore the new fields:

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:

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

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:

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:

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:

var carJson = {"car_brand":"renault"}

And here’s an example of a custom JSON deserializer:

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:

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:

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:

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:

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:

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.

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.

Go deeper into building a REST API with Spring:

>> CHECK OUT THE COURSE