Jackson

Introduction to Jackson ObjectMapper

In this example, we will learn about the ObjectMapper class from Jackson and its capabilities to serialize POJOs(Plain Old Java Objects) into JSON strings and deserialize JSON strings back into Java objects, with some examples.

1. Where To Start?

The first step to getting our hands dirty with ObjectMapper and Jackson data binding is to get the libraries and add them to the classpath. The simplest way would be to add the following dependency to the list of project dependencies in the pom.xml of your maven project.

Dependencies

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

I am using version 2.11.0 which happens to be the latest at the time of writing this article. For other versions check the Maven Central Repository here.

The above dependency will add the following libraries to the classpath:

Jackson ObjectMapper
  • jackson-databind-2.11.0.jar
  • jackson-annotations-2.11.0.jar
  • jackson-core-2.11.0.jar

If you are trying to download the jackson-databind jar manually and adding it to the Java build path, make sure you download and add the other two libraries as well since the data-bind library needs the other two at runtime.

2. The ObjectMapper Class

Serializing a POJO to a JSON string or deserializing a JSON string to an object requires an instance of the ObectMapper class which provides four constructors that can be used to create an instance.

In this example, we will be creating an instance using the default constructor and perform any serialization and deserialization operations.

Creating the ObjectMapper Instance

   ObjectMapper objectMapper = new ObjectMapper();

2.1. Serialize a POJO To JSON String

Throughout this article, we will be using the following Country class for all serialization and deserialization operations.

Country.java

public class Country {

	private String name;
	private long population;
	private int numberOfProvinces;
	private boolean developed;

	public Country(String name, long population, int numberOfProvinces,
						boolean developed) {
		this.name = name;
		this.population = population;
		this.numberOfProvinces = numberOfProvinces;
		this.developed = developed;
	}

   // getters and setters
}

The method writeValueAsString of the ObjectMapper class takes an object as an argument and returns the generated JSON as a String.

POJO To String

		Country country = new Country("India", 135260000000L, 29, true);
		String countryAsString = objectMapper.writeValueAsString(country);
		System.out.println(countryAsString);

Output:

{"name":"India","population":135260000000,"numberOfProvinces":29,"developed":true}

The writeValue method takes two arguments, a File object where the JSON should be written to, and a source object which is to be serialized. Executing this method writes the generated JSON to the File provided.

Serialize POJO As JSON To a File

		objectMapper.writeValue(new File("target/country.json"), country);

		byte[] countryAsBytes = objectMapper.writeValueAsBytes(country);

Similarly, the method writeValueAsBytes serializes a Java object as a byte array.

The ObjectMapper class also provides overloaded methods writeValue that take an argument of the type java.io.OutputStream and java.io.Writer respectively. This value of this argument is used for serializing the java object which is passed as the second argument.

2.2. JSON String To a Java Object

The readValue method of the ObjectMapper class converts a JSON String to a Java Object as shown in the below example. The second argument to the readValue method is of the type Class<T> which is the target class to which the JSON must be deserialized to.

JSON String to Java Object

		String countryStr = "{\"name\":\"India\",\"population\":135260000000,"
				+ "\"numberOfProvinces\":29,\"developed\":true}";
		Country countryFromString = objectMapper.readValue(countryStr, Country.class);

The target class must provide a default constructor for instance creation and if not available, the deserialization process will fail with the error:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.adee.samples.objectmapper.model.Country` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

JSON String In a File To Object

		Country countryFromFile = objectMapper.readValue(
				new File("target/country.json"), Country.class);
		System.out.println("jsonInFileToObject : " + countryFromFile + "\n");

The above code shows an overridden method readValue that takes a File object as an argument. The JSON string contained in this File is read and deserialized to a Java object.

The following are some other variations of the overloaded readValue method which use the Reader, the InputStream, byte[], and the URL respectively to deserialize the JSON to a Java object.

  • readValue(Reader src, Class<T> valueType)
  • readValue(InputStream src, Class<T> valueType)
  • readValue(byte[] src, Class<T> valueType)
  • readValue(URL src, Class<T> valueType)

2.3. JSON String to java.util.Map

A JSON string can be parsed and transformed into a java.util.Map using a TypeReference in the following way.

JSON String To java.util.Map

		String countryStr = "{\"name\":\"India\",\"population\":135260000000,"
				+ "\"numberOfProvinces\":29,\"developed\":true}";
		Map jsonStringToMap = objectMapper.readValue(countryStr,
				new TypeReference<Map>() {
				});
		System.out.println("Country as a Map : " + jsonStringToMap);

Output of the above code

   
Country as a Map : {name=India, population=135260000000,
numberOfProvinces=29, developed=true}

2.4. JSON Array to java.util.List

Similarly, a JSON object that contains an array can be deserialized into a Java object of the type java.util.List. See the example below which demonstrates this.

JSON Array To List

		String countryArrayStr = "[{\"name\":\"India\",\"population\":135260000000,"
				+ "\"numberOfProvinces\":29,\"developed\":true},{\"name\":\"SomeCountry\","
				+ "\"population\":123456789000,\"numberOfProvinces\":45,"
				+ "\"developed\":true}]";
		List countryArrayAsList = objectMapper.readValue
				(countryArrayStr, new TypeReference<List>() {
		});
		System.out.println(countryArrayAsList);

Output of the above code

		[Country [name=India, population=135260000000, numberOfProvinces=29,
		developed=true], Country [name=SomeCountry, population=123456789000,
		numberOfProvinces=45, developed=true]]

2.5. JSON String to JsonNode

A JSON can be also parsed into a com.fasterxml.jackson.databind.JsonNode by invoking the readTree method of the ObjectMapper class and passing the source JSON as an argument. The JsonNode can be further used to retrieve the values of individual fields with the desired type as and when required.

JSON String To JsonNode

		JsonNode jsonNode = objectMapper.readTree(countryStr);
		String name = jsonNode.get("name").asText();
		Long population = jsonNode.get("population").asLong();
		Integer provinces = jsonNode.get("numberOfProvinces").asInt();
		boolean isDeveloped = jsonNode.get("developed").asBoolean();

2.6. Creating a JSON structure

The ObjectMapper class also provides methods to create ObjectNode and ArrayNode and generate a JSON structure as a combination of JsonNode objects. The following code snippet demonstrates this.

Creating a JsonNode Structure

		ObjectNode root = objectMapper.createObjectNode();
		root.put("asText", "SampleString");
		root.put("asBoolean", false);
		ArrayNode array = root.putArray("asArray");
		Country country = new Country("India", 135260000000L, 29, true);
		Country countryFromFile = objectMapper.readValue(
				new File("target/random.json"), Country.class);
		array.addPOJO(country);
		array.addPOJO(countryFromFile);
		System.out.println(objectMapper.writerWithDefaultPrettyPrinter()
				.writeValueAsString(root));

The above code uses the PrettyPrinter and generates the following formatted output, printed nicely as you can see below.

{
  "asText" : "SampleString",
  "asBoolean" : false,
  "asArray" : [ {
    "name" : "India",
    "population" : 135260000000,
    "numberOfProvinces" : 29,
    "developed" : true
  }, {
    "name" : "Dummy",
    "population" : 1987634509,
    "numberOfProvinces" : 15,
    "developed" : true
  } ]
}

3. Configuring The ObjectMapper

There might be scenarios where the input JSON is different from or incompatible with the POJO for the default deserialization process used by the Jackson API. Some of them are as follows:

  • JSON string has fields that are unavailable in the corresponding POJO.
  • JSON string has null values for fields with primitive types.

Let us see what happens when such a JSON is sent for deserialization and how to fix any errors that arise in these cases.

3.1. JSON Fields Unavailable in the POJO

If the JSON string has some fields that are unknown to the POJO, an UnrecognizedPropertyException is thrown by the default deserialization process.

JSON Fields Unavailable in the POJO

		String countryStrUnknownField = "{\"name\":\"India\",\"population\":135260000000,"
				+ "\"numberOfProvinces\":29,\"developed\":true, "
				+ "\"extraField\":\"some-value\"}";
		Country countryUnknownField = objectMapper.readValue(
					countryStrUnknownField, Country.class);

Executing the above code errors out with the message :

Unrecognized field “extraField” (class com.adee.samples.objectmapper.model.Country), not marked as ignorable.

The configure method of the ObjectMapper class allows us to ignore any fields in the JSON string that are unknown to the POJO by using the deserializing feature FAIL_ON_UNKNOWN_PROPERTIES. The following code demonstrates this.

		objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		Country countryUnknownField = objectMapper.readValue
				(countryStrUnknownField, Country.class);
		System.out.println(countryUnknownField);
		// prints; Field extraField is ignored
		Country [name=India, population=135260000000, numberOfProvinces=29, developed=true]

3.2. NULL Values For Primitive Types

Another deserialization feature FAIL_ON_NULL_FOR_PRIMITIVES defines if the primitive types are allowed to hold null values. A value of true for this feature will error out a deserialization operation if the input JSON has null values for primitive types.

NULL Values For Primitive Types

		objectMapper.configure(DeserializationFeature
				.FAIL_ON_NULL_FOR_PRIMITIVES, true);
		String countryStrPrimitiveNull = "{\"name\":\"India\","
				+ "\"population\":135260000000,\"numberOfProvinces\""
				+ ":null,\"developed\":true}";
		countryPrimitiveNull = objectMapper.readValue
					(countryStrPrimitiveNull, Country.class);

The above code when executed fails with a MismatchedInputException: Cannot map `null` into type int. The default value for feature FAIL_ON_NULL_FOR_PRIMITIVES is false.

3.4. Other Deserialization Features

  • FAIL_ON_NUMBERS_FOR_ENUMS feature is used to control if numbers are allowed as enum values for serialization/deserialization.
  • FAIL_ON_IGNORED_PROPERTIES feature, if enabled, throws a JsonMappingException when a property that has been explicitly marked as ignorable in the POJO appears in the JSON string.
  • FAIL_ON_READING_DUP_TREE_KEY feature, if enabled, throws a JsonMappingException if a duplicate key is encountered when transforming JSON content into a tree (JsonNode).

4. Working with Date Formats

Info.java

public class Info {

	private Country country;
	private Date now;

	public Info(Country country, Date now) {
		this.country = country;
		this.now = now;
	}
        
        // getters and setters
}

For demonstrating serialization/deserialization operations for objects with dates, we will consider the above POJO Info which wraps Country and contains a “now” property which is of the type Date. The default serialization of a Date object results in epoch (number of milliseconds since January 1st, 1970, UTC), which is a number and is difficult to read and apprehend as we can see below.

objectWithDateToJsonString {"country":{"name":"India","population":135260000000,
"numberOfProvinces":29,"developed":true},"now":1591758641344}

The ObjectMapper class provides a method setDateFormat that takes an instance of SimpleDateFormat as an argument. The serialization operation after this configuration generates a Date in a human-readable format. Refer to the example below.

ObjectMapper Set DateFormat Serialization

		DateFormat df = new SimpleDateFormat("EEE MMM dd HH:mm:ssZ yyyy");
		objectMapper.setDateFormat(df);
		Info info = new Info(country, new Date());
		String objWithDateAsJsonString = objectMapper.writeValueAsString(info);
		System.out.println(objWithDateAsJsonString);

		// Prints {"country":{"name":"India","population":135260000000,
		//"numberOfProvinces":29,"developed":true},
		//"now":"Wed Jun 10 08:50:42+0530 2020"}

Similarly, for deserializing a Date in a particular format, a SimpleDateFormat object needs to be created and set to the ObjectMapper before deserialization or else an InvalidFormatException will be thrown at runtime.

ObjectMapper Set DateFormat Deseialization

		DateFormat df = new SimpleDateFormat("EEE MMM dd HH:mm:ssZ yyyy");
		objectMapper.setDateFormat(df);
		String infoAsString = "{\"country\":{\"name\":\"India\","
				+ "\"population\":135260000000,\"numberOfProvinces\":29,"
				+ "\"developed\":true},\"now\":\"Tue Jan 01 01:01:01+0230 2020\"}";
		Info info = objectMapper.readValue(infoAsString, Info.class);
		System.out.println("jsonStringWithDateToObject " + info.getNow() + "\n");
		// Prints Wed Jan 01 04:01:01 IST 2020

5. Registering Custom Serializers And Deserializers

The ObjectMapper class provides functionality to register custom serializers and deserializers. Customizing is helpful in scenarios when the source or target JSON structure is different from the Java POJO to which it is deserialized to or is serialized.

5.1. Custom Serializer

CustomCountrySerializer

Below is an implementation of a custom serializer that extends base class StdSerializer. The serialization logic should be written in the overridden serialize method.

		class CustomCountrySerializer extends StdSerializer {

			private static final long serialVersionUID = 1L;

			public CustomCountrySerializer() {
				this(null);
			}

			public CustomCountrySerializer(Class clazz) {
				super(clazz);
			}

			@Override
			public void serialize(Country country, JsonGenerator jsonGenerator,
					SerializerProvider serializer)
					throws IOException {
				jsonGenerator.writeStartObject();
				jsonGenerator.writeStringField("country_name_only_field", 
						country.getName());
				jsonGenerator.writeEndObject();
			}
		}

The custom serializer can be invoked by registering it with the ObjectMapper and using the usual methods for serialization. This is demonstrated in the below example.

Using The Custom Serializer

		ObjectMapper oMapper = new ObjectMapper();
		SimpleModule simpleModule = new SimpleModule("CustomCountrySerializer", new Version(1, 0, 0, null, null, null));
		simpleModule.addSerializer(Country.class, new CustomCountrySerializer());
		oMapper.registerModule(simpleModule);
		String countryJsonFromCustomSerializer = oMapper.writeValueAsString(country);
		System.out.println("demoCustomSerializer : " + countryJsonFromCustomSerializer);

After executing the above code, the following JSON string will be printed.

demoCustomSerializer : {"country_name_only_field":"India"}

5.2. Customer Deserializer

CustomCountryDeserializer

Similarly, below is an example of creating a custom JSON deserializer. The logic for deserialization should be written in the overridden deserialize method.

		class CustomCountryDeserializer extends StdDeserializer {

			private static final long serialVersionUID = 1L;

			public CustomCountryDeserializer() {
				this(null);
			}

			public CustomCountryDeserializer(Class clazz) {
				super(clazz);
			}

			@Override
			public Country deserialize(JsonParser jsonParser,
					DeserializationContext deserializationContext)
					throws IOException {
				Country country = new Country();
				JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
				JsonNode customNameNode = jsonNode.get("customObjectName");
				String name = customNameNode.asText();
				country.setName(name);
				country.setNumberOfProvinces(Integer.MAX_VALUE);
				country.setPopulation(Long.MAX_VALUE);
				return country;
			}
		}

The deserialize method in the above code expects a property customObjectName in the input JSON string that is read and set as the name in the Country object.

Just like the custom serializer, the custom deserializer should be first registered with the ObjectMapper followed by invoking the usual methods of deserialization.

Using the custom Deserializer

		String incompleteCountryJsonStr = "{\"customObjectName\":\"India\"}";
		ObjectMapper oMapper = new ObjectMapper();
		SimpleModule simpleModule = new SimpleModule("CustomCountrySerializer", new Version(1, 0, 0, null, null, null));
		simpleModule.addDeserializer(Country.class, new CustomCountryDeserializer());
		oMapper.registerModule(simpleModule);
		Country country = oMapper.readValue(incompleteCountryJsonStr, Country.class);
		System.out.println("demoCustomDeSerializer : " + country);

After successful deserialization, the println statement will output the following

		demoCustomDeSerializer : Country [name=India, population=9223372036854775807,
				numberOfProvinces=2147483647, developed=false]

6. Summary

In this example, we introduced the ObjectMapper class of the Jackson library for JSON Serialization / Deserialization operations. We saw some of the capabilities of the ObjectMapper and also implemented our custom serializers and deserializers.

7. Download the source code

Download
You can download the full source code of this example here: Introduction to Jackson ObjectMapper

Anmol Deep

Anmol Deep is a senior engineer currently working with a leading identity security company as a Web Developer. He has 8 years of programming experience in Java and related technologies (including functional programming and lambdas) , Python, SpringBoot, Restful architectures, shell scripts, and databases relational(MySQL, H2) and nosql solutions (OrientDB and MongoDB). He is passionate about researching all aspects of software development including technology, design patterns, automation, best practices, methodologies and tools, and love traveling and photography when not coding.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button