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
LocalDateandLocalDateTimetypes. - 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
@JsonIgnoreannotation is added for theignoredElementfield, so it will be ignored during serialization and deserialization if it registersJacksonAnnotationExtension. It can be added to a field or method. - Line 27, 28: the
@JsonPropertyannotation is added at theoverrideNamefield, 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 withoverride_nameif registered withJacksonAnnotationExtension.
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
LocalDateandLocalDateTime. - Line 26, 27, 28, 29: Configure different serializers for
LocalDateandLocalDateTime.
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
LocalDateclass.
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
LocalDatetype. - Line 20: parses the date String into
LocalDateobject.
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
LocalDateTimedata 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, andJSON.Feature.PRESERVE_FIELD_ORDERING.test_serialize_json_annotation– the JSON mapper is registered withJacksonAnnotationExtensionand will process the@JsonProperty,@JsonIgnore, and@JsonPropertyOrderaccordingly.test_serialize_json_custDate– the JSON mapped is created from the customizedMyHandlerProvider, it will map theLocalDateandLocalDateTimeto a single formatted date String.test_serialize_json_annotation_customize– the JSON mapper is registered with bothJacksonAnnotationExtensionand customizedMyHandlerProvider.
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-inJacksonAnnotationExtension, the 2nd one is customizedMyHandlerProvider. - Line 48: verify the “
override_name” is in the serialized JSON String as thePersonannotates with@JsonProperty("override_name"). - Line 49: verify the
LocalDateis serialized to a date String withyyyy-MM-ddformat. - Line 61-62: register
JacksonAnnotationExtensionand 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
LocalDateis serialized to a date String withyyyy-MM-ddformat. - Line 106: verify the default JSON mapper does not print out any
nullelements. - 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
nullelements. - Line 125: verify the
nullelements 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
birthDateis serialized to “yyyy-MM-dd” format by the custom handler. - Line 6, 38, 68: the
ignoredElementis not ignored as the annotation processing is not registered. - Line 7: the
logTimeStampis serialized to ISO 8861 String format by the custom handler. - Line 9, 40, 71: the
overrideNameis not serialized based on the@JsonPropertyas the annotation processing is not registered. - Line 18, 48: the
overrideNameis serialized to “override_name” based on the@JsonPropertyas 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 withJacksonAnnotationExtension.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
Personobject. - Line 41: the Jackson annotations are not processed as the
JacksonAnnotationExtensionis not registered. - Line 62: set the “
overrideName” as Key as the Json mapper didn’t registerJacksonAnnotationExtension. - Line 71: verify the JSON String without annotation setting.
- Line 129 set the “
override_Name” as theKeyas the Json mapper registeredJacksonAnnotationExtension. - 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
composeStringmethod 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
LocalDateandLocalDateTime.
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-jr | Jackson | |
| Annotation Support | limited support | full support |
| Annotation support Jar size | jackson-jr-annotation-support-2.17.1.jar is 21 KB which is smaller | jackson-annotations-2.17.1.jar is 77kb |
| Feature | smaller | full Jackson library |
| started up time | faster | little longer than jackson-jr |
| Modules | basic JSON processing tasks | Including data-binding, streaming, and additional data formats (like XML, CSV, etc.) |
| Tree Mode | No | Yes |
| Stream API | No | Yes |
7. Download
This was an example of a Java Maven project which included a custom date serialization and deserialization with jackson-jr library.
You can download the full source code of this example here: Guide to Jackson-jr Library

