Core Java

A Guide To Jackson-jr Library

1. Introduction

JavaScript Object Notation (JSON) is a text-based format for storing and exchanging data. It’s commonly used by web developers to transfer data between a server and a web application. Jackson and Jackson-jr are open source Java libraries developed by FasterXML, LLC for JSON processing, serialization, and deserialization. Both libraries are fast, lightweight, easy to use, and can handle complex data structure but Jackson provides full-featured support for JSON parsing, generation, and data binding while Jackson-jr is a minimalistic alternative to the full Jackson library. Jackson-jr is designed for resource-constrained environments, such as Android or microservices development. In this example, I will provide a guide to Jackson-jr library with these following topics:

  • Serialize a Java POJO as a JSON String.
  • Deserialize a JSON String into a Java POJO.
  • Customize a JSON serialization and deserialization for LocalDate and LocalDateTime types.
  • Serialize and deserialize complex data.

2. Setup Maven Project

In this step, I will create a Maven project which includes Jackson-jr libraries.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.zheng.demo</groupId>
	<artifactId>lightJackson</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<dependencies>
		<dependency>
			<groupId>com.fasterxml.jackson.jr</groupId>
			<artifactId>jackson-jr-all</artifactId>
			<version>2.17.1</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.jr</groupId>
			<artifactId>jackson-jr-annotation-support</artifactId>
			<version>2.17.1</version>
		</dependency>

		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-api</artifactId>
			<version>5.10.2</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

Note: the jackson-jr-all and jackson-jr-annotation-support libraries are included.

3. Java POJO with Jackson Annotations

3.1 Person with Jackson Annotations

In this step, I will create a Person class which annotates @JsonProperty and @JsonIgnore annotations. Please note that both Jackson and Jackson-jr use the annotations from the com.fasterxml.jackson.annotation package.

Person.java

package lightJackson.data;

import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Person implements Serializable {

	private static final long serialVersionUID = 5963349342478710542L;

	private int age;

	private LocalDate birthDate;

	private String email;

	@JsonIgnore
	private String ignoredElement;

	private LocalDateTime logTimeStamp;

	private String name;

	@JsonProperty("override_name")
	private String overrideName;

	public Person() {
		super();
	}

	public Person(String name, int age) {
		super();
		this.age = age;
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public LocalDate getBirthDate() {
		return birthDate;
	}

	public String getEmail() {
		return email;
	}

	public String getIgnoredElement() {
		return ignoredElement;
	}

	public LocalDateTime getLogTimeStamp() {
		return logTimeStamp;
	}

	public String getName() {
		return name;
	}

	public String getOverrideName() {
		return overrideName;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public void setBirthDate(LocalDate birthDate) {
		this.birthDate = birthDate;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public void setIgnoredElement(String transitData) {
		this.ignoredElement = transitData;
	}

	public void setLogTimeStamp(LocalDateTime logTimeStamp) {
		this.logTimeStamp = logTimeStamp;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setOverrideName(String overrideName) {
		this.overrideName = overrideName;
	}

}
  • Line 20, 21: the @JsonIgnore annotation is added for the ignoredElement field, so it will be ignored during serialization and deserialization if it registers JacksonAnnotationExtension. It can be added to a field or method.
  • Line 27, 28: the @JsonProperty annotation is added at the overrideName field, it specifies the name of the JSON property to be used during serialization and deserialization. So in this example, it will be serialized into JSON String with override_name if registered with JacksonAnnotationExtension.

3.2 Complex Data with Jackson Annotations

Both Jackson and Jackson-jr support the serialization and deserialization for the complex data structure. In this step, I will create a ComplexJsonData class with @JsonPropertyOrder("someName, persons, numbers") to specify JSON elements’ order.

ComplexJsonData.java

package lightJackson.data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder("someName, persons, numbers")
public class ComplexJsonData implements Serializable {

	private static final long serialVersionUID = -8096567618683782284L;

	private List<Integer> numbers = new ArrayList<>();

	private List<Person> persons = new ArrayList<>();

	private String someName;

	public List<Integer> getNumbers() {
		return numbers;
	}

	public List<Person> getPersons() {
		return persons;
	}

	public String getSomeName() {
		return someName;
	}

	public void setNumbers(List<Integer> numbers) {
		this.numbers = numbers;
	}

	public void setPersons(List<Person> person) {
		this.persons = person;
	}

	public void setSomeName(String someName) {
		this.someName = someName;
	}

}
  • Line 9: defines the JSON elements’ order as “someName, persons, numbers“, all other non-specified elements are ordered by the default alphabetical order.

4. Customized Serialization and Deserialization

4.1 Customized Handler Provider

In this step, I will create a MyHandlerProvider class which extends from ReaderWriterProvider and overrides both findValueReader and findValueWriter methods for LocalDate and LocalDateTime data types.

MyHandlerProvider.java

package lightJackson;

import java.time.LocalDate;
import java.time.LocalDateTime;

import com.fasterxml.jackson.jr.ob.api.ReaderWriterProvider;
import com.fasterxml.jackson.jr.ob.api.ValueReader;
import com.fasterxml.jackson.jr.ob.api.ValueWriter;
import com.fasterxml.jackson.jr.ob.impl.JSONReader;
import com.fasterxml.jackson.jr.ob.impl.JSONWriter;

public class MyHandlerProvider extends ReaderWriterProvider {

	@Override
	public ValueReader findValueReader(JSONReader readContext, Class<?> type) {
		if (type.equals(LocalDate.class)) {
			return new CustomDateDeserializer();
		} else if (type.equals(LocalDateTime.class)) {
			return new CustomDateTimeDeserializer();
		}
		return null;
	}

	@Override
	public ValueWriter findValueWriter(JSONWriter writeContext, Class<?> type) {
		if (type == LocalDate.class) {
			return new CustomDateSerializer();
		} else if (type == LocalDateTime.class) {
			return new CustomDateTimeSerializer();
		}
		return null;
	}
}
  • Line 16, 17, 18, 19: Configure different deserializers for LocalDate and LocalDateTime.
  • Line 26, 27, 28, 29: Configure different serializers for LocalDate and LocalDateTime.

4.2 Custom Date Serializer

In this step, I will create a CustomDateSerializer which changes the default LocalDate serialization to a String with the ‘yyyy-MM-dd‘ format.

CustomDateSerializer.java

package lightJackson;

import java.io.IOException;
import java.time.LocalDate;

import com.fasterxml.jackson.jr.ob.api.ValueWriter;
import com.fasterxml.jackson.jr.ob.impl.JSONWriter;
import com.fasterxml.jackson.jr.private_.JsonGenerator;

public class CustomDateSerializer implements ValueWriter {
	@Override
	public Class<?> valueType() {
		return LocalDate.class;
	}

	@Override
	public void writeValue(JSONWriter jsonWriter, JsonGenerator jsonGenerator, Object o) throws IOException {
		jsonGenerator.writeString(o.toString());
	}
}
  • Line 13: returns the LocalDate class.

4.3 Custom Date Deserializer

In this step, I will create a CustomDateDeserializer which deserializes the date String with format of ‘yyyy-mm-dd' to a LocalDate object.

CustomDateDeserializer.java

package lightJackson;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import com.fasterxml.jackson.jr.ob.api.ValueReader;
import com.fasterxml.jackson.jr.ob.impl.JSONReader;
import com.fasterxml.jackson.jr.private_.JsonParser;

public class CustomDateDeserializer extends ValueReader {
	private final static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");

	public CustomDateDeserializer() {
		super(LocalDate.class);
	}

	@Override
	public Object read(JSONReader jsonReader, JsonParser jsonParser) throws IOException {
		return LocalDate.parse(jsonParser.getText(), dtf);
	}
}
  • Line 12: defines the date String format of yyyy-MM-dd.
  • Line 15: defines the LocalDate type.
  • Line 20: parses the date String into LocalDate object.

4.4 Custom DateTime Serializer

In this step, I will create a CustomDateTimeSerializer which serializes the LocalDateTime to a simple String format of ‘yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z”. This step is similar to step 4.2 except the data type is LocalDateTime.

CustomDateTimeSerializer.java

package lightJackson;

import java.io.IOException;
import java.time.LocalDateTime;

import com.fasterxml.jackson.jr.ob.api.ValueWriter;
import com.fasterxml.jackson.jr.ob.impl.JSONWriter;
import com.fasterxml.jackson.jr.private_.JsonGenerator;

public class CustomDateTimeSerializer implements ValueWriter {
	@Override
	public Class<?> valueType() {
		return LocalDateTime.class;
	}

	@Override
	public void writeValue(JSONWriter jsonWriter, JsonGenerator jsonGenerator, Object o) throws IOException {
		jsonGenerator.writeString(o.toString());
	}
}

Note: line 13 uses the LocalDataTime type.

4.5 Custom DateTime Deserializer

In this step, I will create a CustomDateTimeDeserializer which deserializes the date String with ISO8601 format of yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z‘ to a LocalDateTime object. This step is similar to step 4.3.

CustomDateTimeDeserializer.java

package lightJackson;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import com.fasterxml.jackson.jr.ob.api.ValueReader;
import com.fasterxml.jackson.jr.ob.impl.JSONReader;
import com.fasterxml.jackson.jr.private_.JsonParser;

public class CustomDateTimeDeserializer extends ValueReader {
	private final static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'");

	public CustomDateTimeDeserializer() {
		super(LocalDateTime.class);
	}

	@Override
	public Object read(JSONReader jsonReader, JsonParser jsonParser) throws IOException {
		return LocalDateTime.parse(jsonParser.getText(), dtf);
	}
}
  • Line 12: defines the date String format of yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'.
  • line 15, 20: use LocalDateTime data type.

5. Demonstrate with Tests

5.1 Serialize Person Tests

In this step, I will create a SerializePersonTest which includes five junit tests to serialize a Person object into JSON Strings based on the JSON mappers.

  • test_serialize_json_pretty – the JSON mapper is created from the default standard configuration, which does not process any annotation in the POJO.
  • test_serialize_json_withNull – the JSON mapper is created from the default standard configuration with 4 JSON features: JSON.Feature.PRETTY_PRINT_OUTPUT, JSON.Feature.WRITE_NULL_PROPERTIES, JSON.Feature.FAIL_ON_DUPLICATE_MAP_KEYS, and JSON.Feature.PRESERVE_FIELD_ORDERING.
  • test_serialize_json_annotation – the JSON mapper is registered with JacksonAnnotationExtension and will process the @JsonProperty, @JsonIgnore, and @JsonPropertyOrder accordingly.
  • test_serialize_json_custDate – the JSON mapped is created from the customized MyHandlerProvider, it will map the LocalDate and LocalDateTime to a single formatted date String.
  • test_serialize_json_annotation_customize – the JSON mapper is registered with both JacksonAnnotationExtension and customized MyHandlerProvider.

SerializePersonTest.java

package lightJackson;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.jr.annotationsupport.JacksonAnnotationExtension;
import com.fasterxml.jackson.jr.ob.JSON;
import com.fasterxml.jackson.jr.ob.JacksonJrExtension;
import com.fasterxml.jackson.jr.ob.api.ExtensionContext;

import lightJackson.data.Person;

class SerializePersonTest {

	private Person person;

	@BeforeEach
	void setup() {
		person = new Person("Zheng", 30);
		person.setOverrideName("Mary");
		person.setEmail("test@test.com");
		person.setIgnoredElement("should be ignored");
		person.setBirthDate(LocalDate.now());
	}

	@Test
	void test_serialize_json_annotation_customize() {
		JSON annotationCustomizeMapper = JSON.builder().register(JacksonAnnotationExtension.std)
				.register(new JacksonJrExtension() {
					@Override
					protected void register(ExtensionContext extensionContext) {
						extensionContext.insertProvider(new MyHandlerProvider());
					}

				}).build().with(JSON.Feature.PRETTY_PRINT_OUTPUT);

		String json;
		try {
			person.setBirthDate(LocalDate.now());
			json = annotationCustomizeMapper.asString(person);
			assertTrue(json.contains("override_name"), "it should contain override_name elements");
			assertTrue(json.contains("2024-05-17"), "It should display the date as yyyy-mm-dd format");

			System.out.println("JSON from annotCustMapper:\n" + json);
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	@Test
	void test_serialize_json_annotation() {
		try {
			JSON annotationMapper = JSON.builder().register(JacksonAnnotationExtension.std).build()
					.with(JSON.Feature.PRETTY_PRINT_OUTPUT, JSON.Feature.WRITE_NULL_PROPERTIES);
			person.setBirthDate(null);
			String json = annotationMapper.asString(person);
			assertTrue(json.contains("override_name"), "it should contain override_name elements");
			assertFalse(json.contains("should be ignored"), "it should be ignored");

			System.out.println("JSON from annotationMapper:\n" + json);

		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	@Test
	void test_serialize_json_custDate() {
		try {
			person.setLogTimeStamp(LocalDateTime.now());

			JSON customizeMapper = JSON.builder().register(new JacksonJrExtension() {
				@Override
				protected void register(ExtensionContext extensionContext) {
					extensionContext.insertProvider(new MyHandlerProvider());
				}

			}).build().with(JSON.Feature.PRETTY_PRINT_OUTPUT, JSON.Feature.WRITE_NULL_PROPERTIES);

			String json = customizeMapper.asString(person);
			assertTrue(json.contains("2024-05-17"), "It should display the date as yyyy-mm-dd format");

			System.out.println("JSON from customizeMapper:\n" + json);

		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	@Test
	void test_serialize_json_pretty() {
		try {
			JSON prettyMapper = JSON.std.with(JSON.Feature.PRETTY_PRINT_OUTPUT);

			String json = prettyMapper.asString(person);
			assertFalse(json.contains("null"), "it should NOT contain null elements");
			assertTrue(json.contains("should be ignored"), "it should NOT be ignored without annotation");
			assertFalse(json.contains("override_Name"), "it should NOT contain override_Name elements");
			System.out.println("JSON from prettyMapper:\n" + json);

		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	@Test
	void test_serialize_json_withNull() {
		try {
			JSON prettyMapperWithNull = JSON.std.with(JSON.Feature.PRETTY_PRINT_OUTPUT,
					JSON.Feature.WRITE_NULL_PROPERTIES, JSON.Feature.FAIL_ON_DUPLICATE_MAP_KEYS,
					JSON.Feature.PRESERVE_FIELD_ORDERING);

			String json = prettyMapperWithNull.asString(person);
			assertTrue(json.contains("null"), "it should contain null elements");
			System.out.println("JSON from prettyMapperWithNull:\n" + json);

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
  • Line 35-42: register two JacksonJrExtensions. The first one is built-in JacksonAnnotationExtension, the 2nd one is customized MyHandlerProvider.
  • Line 48: verify the “override_name” is in the serialized JSON String as the Person annotates with @JsonProperty("override_name").
  • Line 49: verify the LocalDate is serialized to a date String with yyyy-MM-dd format.
  • Line 61-62: register JacksonAnnotationExtension and with two JSON features: JSON.Feature.PRETTY_PRINT_OUTPUT, JSON.Feature.WRITE_NULL_PROPERTIES
  • Line 81-85: register with customized MyHandlerProvider.
  • Line 90: verify the LocalDate is serialized to a date String with yyyy-MM-dd format.
  • Line 106: verify the default JSON mapper does not print out any null elements.
  • Line 107, 108: verify the default JSON mapper does not process annotations as the mapper is not registered with the JacksonAnnotationExtension .
  • Line 120-122: defines JSON mapper to write the null elements.
  • Line 125: verify the null elements are included in the JSON String.

Execute the Junit test and capture the output

Junit test output for SerializePersonTest

JSON from customizeMapper:
{
  "age" : 30,
  "birthDate" : "2024-05-17",
  "email" : "test@test.com",
  "ignoredElement" : "should be ignored",
  "logTimeStamp" : "2024-05-17T13:53:50.205143400",
  "name" : "Zheng",
  "overrideName" : "Mary"
}
JSON from annotationMapper:
{
  "age" : 30,
  "birthDate" : null,
  "email" : "test@test.com",
  "logTimeStamp" : null,
  "name" : "Zheng",
  "override_name" : "Mary"
}
JSON from prettyMapper:
{
  "age" : 30,
  "birthDate" : {
    "chronology" : {
      "calendarType" : "iso8601",
      "id" : "ISO"
    },
    "dayOfMonth" : 17,
    "dayOfWeek" : "FRIDAY",
    "dayOfYear" : 138,
    "era" : "CE",
    "leapYear" : true,
    "month" : "MAY",
    "monthValue" : 5,
    "year" : 2024
  },
  "email" : "test@test.com",
  "ignoredElement" : "should be ignored",
  "name" : "Zheng",
  "overrideName" : "Mary"
}
JSON from annotCustMapper:
{
  "age" : 30,
  "birthDate" : "2024-05-17",
  "email" : "test@test.com",
  "name" : "Zheng",
  "override_name" : "Mary"
}
JSON from prettyMapperWithNull:
{
  "age" : 30,
  "birthDate" : {
    "chronology" : {
      "calendarType" : "iso8601",
      "id" : "ISO"
    },
    "dayOfMonth" : 17,
    "dayOfWeek" : "FRIDAY",
    "dayOfYear" : 138,
    "era" : "CE",
    "leapYear" : true,
    "month" : "MAY",
    "monthValue" : 5,
    "year" : 2024
  },
  "email" : "test@test.com",
  "ignoredElement" : "should be ignored",
  "logTimeStamp" : null,
  "name" : "Zheng",
  "overrideName" : "Mary"
}
  • Line 4: the birthDate is serialized to “yyyy-MM-dd” format by the custom handler.
  • Line 6, 38, 68: the ignoredElement is not ignored as the annotation processing is not registered.
  • Line 7: the logTimeStamp is serialized to ISO 8861 String format by the custom handler.
  • Line 9, 40, 71: the overrideName is not serialized based on the @JsonProperty as the annotation processing is not registered.
  • Line 18, 48: the overrideName is serialized to “override_name” based on the @JsonProperty as the annotation processing is registered.
  • Line 23-35: the default LocalDate is serialized to nested object.Line

5.2 Deserialize Person Tests

In this step, I will create a DeserializePersonTest which includes four junit tests to deserialize a JSON String into a Person object.

  • test_deserialize_json_via_annotation – verify the annotated fields are deserialized accordingly since the JSON mapper is registered with JacksonAnnotationExtension.
  • test_deserialize_json_via_standard – verify the deserialization works fine from the default configuration.
  • test_deserialize_json_with_cust – verify the deserialization works as expected for the customized handler.
  • test_deserialize_json_with_cust_anno – verify the deserialization works as expected for a JSON mapper with two registered extensions.

DeserializePersonTest.java

package lightJackson;

import static org.junit.Assert.assertNull;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.jr.annotationsupport.JacksonAnnotationExtension;
import com.fasterxml.jackson.jr.ob.JSON;
import com.fasterxml.jackson.jr.ob.JacksonJrExtension;
import com.fasterxml.jackson.jr.ob.api.ExtensionContext;

import lightJackson.data.Person;

class DeserializePersonTest {

	@Test
	void test_deserialize_json_via_annotation() {
		try {

			String json = JSON.std.with(JSON.Feature.PRETTY_PRINT_OUTPUT).asString(new LinkedHashMap<String, Object>() {
				{
					put("name", "John Doe");
					put("age", 30);
					put("ignoredElement", "shout NOT be mapped");
					put("email", "johndoe@example.com");
					put("override_name", "override Mary");
				}
			});

			Person person = JSON.builder().register(JacksonAnnotationExtension.std).build()
					.with(JSON.Feature.PRETTY_PRINT_OUTPUT).beanFrom(Person.class, json);

			assertEquals("John Doe", person.getName());
			assertEquals(30, person.getAge());
			assertEquals("johndoe@example.com", person.getEmail());
			assertEquals("override Mary", person.getOverrideName());
			assertNull(person.getIgnoredElement());
			assertNull(person.getBirthDate());
			assertNull(person.getLogTimeStamp());
		
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Test
	void test_deserialize_json_via_standard() {
		try {

			String json = JSON.std.with(JSON.Feature.PRETTY_PRINT_OUTPUT).asString(new LinkedHashMap<String, Object>() {
				{
					put("name", "John Doe");
					put("age", 30);
					put("email", "johndoe@example.com");
					put("ignoredElement", "should be mapped");
					put("overrideName", "override Mary");
				}
			});

			Person person = JSON.std.with(JSON.Feature.PRETTY_PRINT_OUTPUT).beanFrom(Person.class, json);

			assertEquals("John Doe", person.getName());
			assertEquals(30, person.getAge());
			assertEquals("johndoe@example.com", person.getEmail());
			assertEquals("should be mapped", person.getIgnoredElement());
			assertEquals("override Mary", person.getOverrideName());
			assertNull(person.getBirthDate());
			assertNull(person.getLogTimeStamp());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Test
	void test_deserialize_json_with_cust() {
		try {

			String json = JSON.std.with(JSON.Feature.PRETTY_PRINT_OUTPUT).asString(new LinkedHashMap<String, Object>() {
				{
					put("name", "John Doe");
					put("age", 30);
					put("email", "johndoe@example.com");
					put("birthDate", "1980-12-12");
					put("logTimeStamp", "2014-12-09T13:50:51.644000Z");
				}
			});

			JSON customizeMapper = JSON.builder().register(new JacksonJrExtension() {
				@Override
				protected void register(ExtensionContext extensionContext) {
					extensionContext.insertProvider(new MyHandlerProvider());
				}

			}).build().with(JSON.Feature.PRETTY_PRINT_OUTPUT, JSON.Feature.WRITE_NULL_PROPERTIES);

			Person person = customizeMapper.beanFrom(Person.class, json);

			assertEquals("John Doe", person.getName());
			assertEquals(30, person.getAge());
			assertEquals("johndoe@example.com", person.getEmail());
			assertEquals("1980-12-12", person.getBirthDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
			assertEquals("2014-12-09T13:50:51.644000Z",
					person.getLogTimeStamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'")));
			assertNull(person.getIgnoredElement());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Test
	void test_deserialize_json_with_cust_anno() {
		try {

			String json = JSON.std.with(JSON.Feature.PRETTY_PRINT_OUTPUT).asString(new LinkedHashMap<String, Object>() {
				{
					put("name", "John Doe");
					put("age", 30);
					put("email", "johndoe@example.com");
					put("birthDate", "1980-12-12");
					put("logTimeStamp", "2014-12-09T13:50:51.644000Z");
					put("override_name", "override Mary");
				}
			});

			JSON customizeMapper = JSON.builder().register(JacksonAnnotationExtension.std)
					.register(new JacksonJrExtension() {
						@Override
						protected void register(ExtensionContext extensionContext) {
							extensionContext.insertProvider(new MyHandlerProvider());
						}

					}).build().with(JSON.Feature.PRETTY_PRINT_OUTPUT, JSON.Feature.WRITE_NULL_PROPERTIES);

			Person person = customizeMapper.beanFrom(Person.class, json);

			assertEquals("John Doe", person.getName());
			assertEquals(30, person.getAge());
			assertEquals("johndoe@example.com", person.getEmail());
			assertEquals("override Mary", person.getOverrideName());
			assertEquals("1980-12-12", person.getBirthDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
			assertEquals("2014-12-09T13:50:51.644000Z",
					person.getLogTimeStamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'")));
			assertNull(person.getIgnoredElement());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
  • Line 31: the “override_name” is defined with @JsonProperty.
  • Line 35: the Json mapper is registered with JacksonAnnotationExtension.
  • Line 36: serializes to JSON String from a Person object.
  • Line 41: the Jackson annotations are not processed as the JacksonAnnotationExtension is not registered.
  • Line 62: set the “overrideName” as Key as the Json mapper didn’t register JacksonAnnotationExtension.
  • Line 71: verify the JSON String without annotation setting.
  • Line 129 set the “override_Name” as the Key as the Json mapper registered JacksonAnnotationExtension.
  • Line 147, 148, 149: verify the JSON String with annotation setting and customized date setting.

Execute the Junit test and all tests passed as expected.

5.3 Complex Data Tests

In this step, I will create a ComplexJsonTest class to test the complex data objects’s serialization and deserialization. This step is similar to steps 5.1 and 5.2. The difference is that Json is created from the composeString method.

ComplexJsonData.java

package lightJackson;

import java.io.IOException;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.jr.annotationsupport.JacksonAnnotationExtension;
import com.fasterxml.jackson.jr.ob.JSON;

import lightJackson.data.ComplexJsonData;

class ComplexJsonTest {

	String json;

	@BeforeEach
	void setup() {
		try {
			json = JSON.std.with(JSON.Feature.PRETTY_PRINT_OUTPUT).composeString().startObject()
					.startArrayField("persons").startObject().put("name", "name1").put("age", 11).end().startObject()
					.put("name", "name2").put("age", 12).end().end().startArrayField("numbers").add(1).add(2).add(3)
					.end().put("someName", "Test").end().finish();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Test
	void test_composejson_for_complex() {
		try {
			System.out.println("Original Json:\n" + json);

			// De-serialization
			ComplexJsonData complexDataObj = JSON.std.with(JSON.Feature.PRETTY_PRINT_OUTPUT)
					.beanFrom(ComplexJsonData.class, json);

			// Serialization
			String newJson = JSON.std
					.with(JSON.Feature.PRETTY_PRINT_OUTPUT, JSON.Feature.WRITE_NULL_PROPERTIES,
							JSON.Feature.PRESERVE_FIELD_ORDERING, JSON.Feature.FAIL_ON_DUPLICATE_MAP_KEYS)
					.asString(complexDataObj);
			System.out.println("Serialization Json:\n" + newJson);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Test
	void test_json_to_complex_keepOrder() {
		try {
			System.out.println("Original Json:\n" + json);

			// De-serialization
			ComplexJsonData complexDataObj = JSON.std.with(JSON.Feature.PRETTY_PRINT_OUTPUT)
					.beanFrom(ComplexJsonData.class, json);

			// Serialization
			String newJson = JSON.builder().register(JacksonAnnotationExtension.std).build()
					.with(JSON.Feature.PRETTY_PRINT_OUTPUT, JSON.Feature.WRITE_NULL_PROPERTIES,
							JSON.Feature.PRESERVE_FIELD_ORDERING, JSON.Feature.FAIL_ON_DUPLICATE_MAP_KEYS)
					.asString(complexDataObj);
			System.out.println("Serialization Json:\n" + newJson);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
  • Line 20: invokes the composeString method to create a JSON String.
  • Line 21 – 23: Uses the builder pattern to populate the JSON String.

Execute the Junit test and capture the output. The output meets the expectation.

ComplexJsonTest Junit test output

Original Json:
{
  "persons" : [ {
    "name" : "name1",
    "age" : 11
  }, {
    "name" : "name2",
    "age" : 12
  } ],
  "numbers" : [ 1, 2, 3 ],
  "someName" : "Test"
}
Serialization Json:
{
  "someName" : "Test",
  "numbers" : [ 1, 2, 3 ],
  "persons" : [ {
    "age" : 11,
    "birthDate" : null,
    "email" : null,
    "logTimeStamp" : null,
    "name" : "name1",
    "override_name" : null
  }, {
    "age" : 12,
    "birthDate" : null,
    "email" : null,
    "logTimeStamp" : null,
    "name" : "name2",
    "override_name" : null
  } ]
}
Original Json:
{
  "persons" : [ {
    "name" : "name1",
    "age" : 11
  }, {
    "name" : "name2",
    "age" : 12
  } ],
  "numbers" : [ 1, 2, 3 ],
  "someName" : "Test"
}
Serialization Json:
{
  "numbers" : [ 1, 2, 3 ],
  "persons" : [ {
    "age" : 11,
    "birthDate" : null,
    "email" : null,
    "ignoredElement" : null,
    "logTimeStamp" : null,
    "name" : "name1",
    "overrideName" : null
  }, {
    "age" : 12,
    "birthDate" : null,
    "email" : null,
    "ignoredElement" : null,
    "logTimeStamp" : null,
    "name" : "name2",
    "overrideName" : null
  } ],
  "someName" : "Test"
}

Note: when a JSON mapper is registered with JacksonAnnotationExtension, then the elements are ordered based on @JsonPropertyOrder("someName, persons, numbers"), otherwise, the elements are based on the default order.

6. Conclusion

In this example, I outlined several main steps as a user guide to Jackson-jr library:

  • Include the jackson-jar as dependency.
  • Annotates the JSON properties in the Java POJO objects.
  • Serialize/deserialize JSON based on the JSON mapper object.
  • Create a customized serialize/deserialize for LocalDate and LocalDateTime.

As you see in step 5, It’s just a few lines of code to serialize and deserialize Java POJO to JSON. Table 1 outlines the difference between these 2 libraries.

Jackson-jrJackson
Annotation Supportlimited supportfull support
Annotation support Jar sizejackson-jr-annotation-support-2.17.1.jar is 21 KB which is smallerjackson-annotations-2.17.1.jar is 77kb
Featuresmallerfull Jackson library
started up timefasterlittle longer than jackson-jr
Modulesbasic JSON processing tasksIncluding data-binding, streaming, and additional data formats (like XML, CSV, etc.)
Tree ModeNoYes
Stream APINoYes
Table 1, Jackson-jr vs Jackson

7. Download

This was an example of a Java Maven project which included a custom date serialization and deserialization with jackson-jr library.

Download
You can download the full source code of this example here: Guide to Jackson-jr Library

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

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

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button