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
andLocalDateTime
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 theignoredElement
field, 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
@JsonProperty
annotation is added at theoverrideName
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 withoverride_name
if 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
LocalDate
andLocalDateTime
. - Line 26, 27, 28, 29: Configure different serializers for
LocalDate
andLocalDateTime
.
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
, andJSON.Feature.PRESERVE_FIELD_ORDERING
.test_serialize_json_annotation
– the JSON mapper is registered withJacksonAnnotationExtension
and will process the@JsonProperty
,@JsonIgnore
, and@JsonPropertyOrder
accordingly.test_serialize_json_custDate
– the JSON mapped is created from the customizedMyHandlerProvider
, it will map theLocalDate
andLocalDateTime
to a single formatted date String.test_serialize_json_annotation_customize
– the JSON mapper is registered with bothJacksonAnnotationExtension
and 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
JacksonJrExtension
s. The first one is built-inJacksonAnnotationExtension
, the 2nd one is customizedMyHandlerProvider
. - Line 48: verify the “
override_name
” is in the serialized JSON String as thePerson
annotates with@JsonProperty("override_name")
. - Line 49: verify the
LocalDate
is serialized to a date String withyyyy-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 withyyyy-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 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
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 registerJacksonAnnotationExtension
. - Line 71: verify the JSON String without annotation setting.
- Line 129 set the “
override_Name
” as theKey
as 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
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
andLocalDateTime
.
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