How to The Jackson Object Mapper with JSON
0 CommentsLast Updated on October 22, 2024 by jt
Introduction
As a Java developer, a common task you’ll encounter is converting between Java objects and JSON. The Jackson library is a powerful and widely used tool for this purpose. Whether you’re building RESTful APIs, microservices, or simply handling JSON data in your application, Jackson’s ObjectMapper
class makes it easy to serialize and deserialize Java objects into JSON and vice versa.
In this blog post, I’ll walk you through how to get started with Jackson’s ObjectMapper
, its key features, and best practices for its use in Java projects.
What is Jackson?
Jackson is a high-performance, flexible JSON processing library for Java. It known for its ease of use and integration with popular frameworks like Spring Boot. Jackson supports converting Java objects to JSON (serialization), and JSON to Java objects (deserialization). Jackson also supports a variety of other formats such as Avro, BSON, CSV, Java Properties, Protobuf, YAML, and XML.
The core class to perform is ObjectMapper
, a versatile tool for reading and writing JSON data.
Adding Jackson to Your Project
To use Jackson in your Java project, add the following dependencies.
Maven
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
Gradle
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
Note: for up-to-date release info, see Jackson Releases.
Adding the jackson-databind
dependency to your project adds two transitive dependencies:
jackson-annotations
jackson-core
Using the Jackson ObjectMapper
Writing Json – Serialization with Jackson
Serialization is the process of converting a Java object into JSON. Remember, in Java everything is an object. Thus, Jackson’s object mapper can convert Java Maps, Lists, Java Types, Java Records, and of course Java POJOs.
Write a Java Map to Json
The Serialization of Java Maps is a very easy way to generate JSON using the Jackson Object mapper. When you need to create a JSON payload to test a RESTful API, using Java Maps is a great way to get started.
Consider the following example:
public class ObjectMapperTests {
@Test
void testMapToJson() throws JsonProcessingException {
Map<String, Object> person = Map.of(
"firstName", "Jimmy",
"lastName", "Buffett",
"address", Map.of(
"addressLine1", "500 Duval Street",
"city", "Key West",
"state", "FL",
"zip", "33040"
)
);
ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.writeValueAsString(person));
}
}
-----------------output----------------
{"lastName":"Buffett","address":{"city":"Key West","zip":"33040","addressLine1":"500 Duval Street","state":"FL"},"firstName":"Jimmy"}
In the example above we create a Map
with a key using a String
, and values of any object. The first two properties are simple Strings
, while the address is a nested Map
.
In the last two lines, you can see we create a new ObjectMapper instance, and use it to serialize (writeValueAsString
) the Map
value to a JSON string, which is outputted to the console.
From the output you can see we have a proper JSON object whose property names are inherited from the Map keys.
Write a Java List to a JSON Array
Jackson’s objectMapper can also easily produce JSON arrays from Java lists.
Consider the following example:
@Test
void testListOfMapsToJson() throws JsonProcessingException {
Map<String, Object> person1 = Map.of(
"firstName", "Jimmy",
"lastName", "Buffett",
"address", Map.of(
"addressLine1", "500 Duval Street",
"city", "Key West",
"state", "FL",
"zip", "33040"
)
);
Map<String, Object> person2 = Map.of(
"firstName", "Jimmy",
"lastName", "Buffett",
"address", Map.of(
"addressLine1", "500 Duval Street",
"city", "Key West",
"state", "FL",
"zip", "33040"
)
);
ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.writeValueAsString(List.of(person1, person2)));
}
-----------------------output------------------------
[{"firstName":"Jimmy","lastName":"Buffett","address":{"addressLine1":"500 Duval Street","state":"FL","city":"Key West","zip":"33040"}},{"firstName":"Jimmy","lastName":"Buffett","address":{"addressLine1":"500 Duval Street","state":"FL","city":"Key West","zip":"33040"}}]
Using the same Map
structure as the previous example, we simply create two Java Maps
and add them to a list. You can see from the output the objectMapper
created a JSON array from the Java List
.
Write a Java POJO to JSON
Writing Java POJOs to JSON is just as easy as Java Maps.
First, let’s create two Java POJOs. We will use the same data structures as our Map examples. We’ll leverage Project Lombok to add functionality and reduce cerimonial code.
First we need to create the POJO classes:
@Data
@Builder
@AllArgsConstructor
public static class Address {
private String addressLine1;
private String city;
private String state;
private String zip;
}
@Data
@Builder
@AllArgsConstructor
public static class Person {
private String firstName;
private String lastName;
private String phoneNumber;
private Address address;
}
Next, let’s write a quick test to populate a Person POJO and serialize it to JSON.
@Test
void testObjectToJson() throws JsonProcessingException {
Person person = Person.builder()
.firstName("Jimmy")
.lastName("Buffett")
.address(Address.builder()
.addressLine1("500 Duval Street")
.city("Key West")
.state("FL")
.zip("33040")
.build())
.build();
ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.writeValueAsString(person));
}
---------------------output-----------------------
{"firstName":"Jimmy","lastName":"Buffett","address":{"addressLine1":"500 Duval Street","city":"Key West","state":"FL","zip":"33040"}}
Pretty Print JSON Output
If we wish to make the output easier to read, we can instruct the objectMapper
to “Pretty Print” the output. We simply need to use the writerWithDefaultPrettyPrinter()
method as follows:
System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(person));
--------------------output-----------------------
{
"firstName" : "Jimmy",
"lastName" : "Buffett",
"address" : {
"addressLine1" : "500 Duval Street",
"city" : "Key West",
"state" : "FL",
"zip" : "33040"
}
}
Write List of Java POJOs to a JSON Array
Writing a list of Java objects to a JSON array is just as easy. Consider the following test:
@Test
void testListOfObjectsToJson() throws JsonProcessingException {
Person person1 = Person.builder()
.firstName("Jimmy")
.lastName("Buffett")
.address(Address.builder()
.addressLine1("500 Duval Street")
.city("Key West")
.state("FL")
.zip("33040")
.build())
.build();
Person person2 = Person.builder()
.firstName("Jimmy")
.lastName("Buffett")
.address(Address.builder()
.addressLine1("500 Duval Street")
.city("Key West")
.state("FL")
.zip("33040")
.build())
.build();
ObjectMapper objectMapper = new ObjectMapper();
System.out.println(
objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(List.of(person1, person2)));
}
--------------------output----------------------
[ {
"firstName" : "Jimmy",
"lastName" : "Buffett",
"address" : {
"addressLine1" : "500 Duval Street",
"city" : "Key West",
"state" : "FL",
"zip" : "33040"
}
}, {
"firstName" : "Jimmy",
"lastName" : "Buffett",
"address" : {
"addressLine1" : "500 Duval Street",
"city" : "Key West",
"state" : "FL",
"zip" : "33040"
}
} ]
We can see from the output a JSON array is created with two Person objects.
Write JSON with a Jackson Custom Serializer
We also have the option to use Custom Serializers with Jackson. By using Custom Serializers, we can full control over the JSON that is produced. You may wish to rename properties, add constants, or add some type of conditional logic.
To create a Custom Serializer, we need to provide an implementation that extends Jackson’s StdSerializer class. Following is an example of a Custom Serializer for our Address class.
public static class AddressSerializer extends StdSerializer<Address> {
public AddressSerializer() {
this(null);
}
public AddressSerializer(Class<Address> t) {
super(t);
}
@Override
public void serialize(Address address, JsonGenerator jsonGenerator, SerializerProvider
serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("line1", address.getAddressLine1());
jsonGenerator.writeStringField("city", address.getCity());
jsonGenerator.writeStringField("state", address.getState());
jsonGenerator.writeStringField("postalCode", address.getZip());
jsonGenerator.writeStringField("country", "USA");
jsonGenerator.writeStringField("version", "1.0");
jsonGenerator.writeEndObject();
}
}
In the above Custom Serializer implementation, we are overriding a couple property names, and adding a couple constants to the output.
The following example shows how we can use the Custom Serializer.
@Test
void testObjectToJsonWithCustomSerializer() throws JsonProcessingException {
Person person = Person.builder()
.firstName("Jimmy")
.lastName("Buffett")
.address(Address.builder()
.addressLine1("500 Duval Street")
.city("Key West")
.state("FL")
.zip("33040")
.build())
.build();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new
com.fasterxml.jackson.databind.module.SimpleModule()
.addSerializer(Address.class, new AddressSerializer()));
System.out.println(objectMapper
.writerWithDefaultPrettyPrinter().writeValueAsString(person));
}
----------------output-------------------
{
"firstName" : "Jimmy",
"lastName" : "Buffett",
"address" : {
"line1" : "500 Duval Street",
"city" : "Key West",
"state" : "FL",
"postalCode" : "33040",
"country" : "USA",
"version" : "1.0"
}
}
The key take away to note, is we are registering the Custom Serializer with the objectMapper
as a new Module. Once registered as a new module, we can see the objectMapper
outputs the customized field values.
Reading Json – Deserialization with Jackson
Deserialization is the process of converting a JSON string into a Java object.
Read Json to Java Map
We can use the following test to demonstrate reading a JSON string to a Java Map.
@Test
void testJsonToMap() throws JsonProcessingException {
String json = """
{"lastName":"Buffett","address":{"city":"Key West","zip":"33040","addressLine1":"500 Duval Street","state":"FL"},"firstName":"Jimmy"}""";
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> person = objectMapper.readValue(json, Map.class);
System.out.println(person);
}
------------------output------------------
{lastName=Buffett, address={city=Key West, zip=33040, addressLine1=500 Duval Street, state=FL}, firstName=Jimmy}
We can examine the execution in the IntelliJ debugger to see the Jackson ObjectMapper
mapped the JSON string to a Java LinkedHashMap
:
Read JSON to Jackson JsonNode
JsonNode is a Jackson type used to represent JSON data as a Java type. JsonNode is an immutable object graph generated from JSON.
The following is an example of reading JSON into the JsonNode type.
@Test
void testJsonToNode() throws JsonProcessingException {
String json = """
{"lastName":"Buffett","address":{"city":"Key West","zip":"33040","addressLine1":"500 Duval Street","state":"FL"},"firstName":"Jimmy"}""";
ObjectMapper objectMapper = new ObjectMapper();
JsonNode person = objectMapper.readTree(json);
System.out.println(person);
System.out.println(person.get("firstName").asText());
System.out.println(person.get("address").get("city").asText());
}
----------------output-----------------
{"lastName":"Buffett","address":{"city":"Key West","zip":"33040","addressLine1":"500 Duval Street","state":"FL"},"firstName":"Jimmy"}
Jimmy
Key West
You can see from the above example:
- The
objectMapper.readTree()
method creates the JsonNode instance - The
JsonNode
toString()
method outputs JSON (implicit withSystem.out.println()
) - We navigate the object graph using the get() method.
Read JSON to Java Object
Jackson also makes it easy to read JSON directly to the desired Java POJO.
For Jackson to easily read JSON to our POJOs, we need to add a no args constructor. We can take care of this by adding the Project Lombok @NoArgsConstructor annotation to our POJOs as follows
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Address {
private String addressLine1;
private String city;
private String state;
private String zip;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Person {
private String firstName;
private String lastName;
private Address address;
}
Here is an example of reading a JSON payload back into our Person
POJO.
@Test
void testJsonToObject() throws JsonProcessingException {
String json = """
{"firstName":"Jimmy","lastName":"Buffett","address":{"addressLine1":"500 Duval
Street","city":"Key West","state":"FL","zip":"33040"}}""";
ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue(json, Person.class);
System.out.println(person);
}
----------------output---------------
ObjectMapperTests.Person(firstName=Jimmy, lastName=Buffett, address=ObjectMapperTests.Address(addressLine1=500 Duval Street, city=Key West, state=FL, zip=33040))
Note: We annotated our POJOs with the Project Lombok @Data annotation. This annotation adds a toString()
method to our POJOs, which is creating the output we see in the above example.
Read Json Array to Java List
Using the objectMapper, we can also easily consume JSON arrays to Lists of POJOs.
The following example will read a JSON array of Person objects and read it into a list of people.
@Test
void testJsonArrayToListOfObjects() throws JsonProcessingException {
String json = """
[{"firstName":"Jimmy","lastName":"Buffett","address":{"addressLine1":
"500 Duval Street","city":"Key West","state":"FL","zip":"33040"}},
{"firstName":"Jimmy","lastName":"Buffett","address":{"addressLine1":
"500 Duval Street","city":"Key West","state":"FL","zip":"33040"}}]""";
ObjectMapper objectMapper = new ObjectMapper();
List<Person> people = objectMapper.readValue(json,
objectMapper.getTypeFactory().constructCollectionType(List.class, Person.class));
System.out.println(people);
}
---------------------output-----------------------
[ObjectMapperTests.Person(firstName=Jimmy, lastName=Buffett, address=ObjectMapperTests.Address(addressLine1=500 Duval Street, city=Key West, state=FL, zip=33040)), ObjectMapperTests.Person(firstName=Jimmy, lastName=Buffett, address=ObjectMapperTests.Address(addressLine1=500 Duval Street, city=Key West, state=FL, zip=33040))]
In the above example, you can see we are getting the TypeFactory from the objectMappe
r and instructing it to use the collection type with List.class
and Person.class
.
An alternative is to use TypeReference
as demonstrated in the following example:
@Test
void testJsonArrayToListOfObjectsTypeReference() throws JsonProcessingException {
String json = """
[{"firstName":"Jimmy","lastName":"Buffett","address":{"addressLine1":
"500 Duval Street","city":"Key West","state":"FL","zip":"33040"}},
{"firstName":"Jimmy","lastName":"Buffett","address":{"addressLine1":
"500 Duval Street","city":"Key West","state":"FL","zip":"33040"}}]""";
ObjectMapper objectMapper = new ObjectMapper();
List<Person> people = objectMapper.readValue(json, new
com.fasterxml.jackson.core.type.TypeReference<List<Person>>() {});
System.out.println(people);
}
----------------------output-------------------------
[ObjectMapperTests.Person(firstName=Jimmy, lastName=Buffett, address=ObjectMapperTests.Address(addressLine1=500 Duval Street, city=Key West, state=FL, zip=33040)), ObjectMapperTests.Person(firstName=Jimmy, lastName=Buffett, address=ObjectMapperTests.Address(addressLine1=500 Duval Street, city=Key West, state=FL, zip=33040))]
In the second example we are creating a TypeReference
with Java generics of List
and Person
.
Read JSON to Java POJO Using and Jackson Custom Deserializer
We can also use Custom Deserializers with Jackson. The process is very similar to using Custom Serializers. By using Custom Deserializers we have full programmatic control over how the POJO is created from the JSON payload. We can map properties, perform type conversions, ignore unused properties, and apply other conditional logic.
In this example, we will write a Custom Deserializer to deserialize the JSON we previously created with the Custom Serializer.
To create a Custom Deserializer, we need to create a new Java class that extends the Jackson StdDeserializer
class.
Following is our implementation of a Custom Deserializer for the Address
class.
public static class AddressDeserializer extends StdDeserializer<Address> {
protected AddressDeserializer(Class<?> vc) {
super(vc);
}
@Override
public Address deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext)
throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
return Address.builder()
.addressLine1(node.get("line1").asText())
.city(node.get("city").asText())
.state(node.get("state").asText())
.zip(node.get("postalCode").asText())
.build();
}
}
In the following example, we register the Custom Deserializer as a new module with the objectMapper.
@Test
void testJsonToObjectWithCustomDeserializer() throws JsonProcessingException {
String json = """
{"firstName":"Jimmy","lastName":"Buffett","address":{"line1":"500 Duval
Street","city":"Key West","state":"FL","postalCode":"33040"}}""";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new SimpleModule().addDeserializer(Address.class,
new AddressDeserializer(Address.class)));
Person person = objectMapper.readValue(json, Person.class);
System.out.println(person);
}
---------------------output--------------------
ObjectMapperTests.Person(firstName=Jimmy, lastName=Buffett, address=ObjectMapperTests.Address(addressLine1=500 Duval Street, city=Key West, state=FL, zip=33040))
Read JSON to POJO Ignoring Unknown Properties
By default, the Jackson objectMapper will throw an exception if there are unknown properties in the JSON payload. If you are consuming data from a third party source, you won’t have any control if they add a property. Or you might not be interested in using some additional properties.
Ignoring Unknown properties with Jackson is easy enough with a little configuration. In the following example, we update the objectMapper configuration to ignore unknown properties.
@Test
void testJsonToObjectWithUnknownProperties() throws JsonProcessingException {
String json = """
{"firstName":"Jimmy","lastName":"Buffett","address":{"addressLine1":
"500 Duval Street","city":"Key West","state":"FL","zip":"33040"},
"foo":"bar"}}""";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Person person = objectMapper.readValue(json, Person.class);
System.out.println(person);
}
---------------output----------------------
ObjectMapperTests.Person(firstName=Jimmy, lastName=Buffett, address=ObjectMapperTests.Address(addressLine1=500 Duval Street, city=Key West, state=FL, zip=33040))
In the above example we can see the JSON payload has an additional property of "foo":"bar"
. By setting DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
to false, the JSON payload is deserialized without exception.
Using the Jackson Object Mapper with Spring Boot
When the Jackson jackson-databind
dependency is on the classpath, Spring Boot will autoconfigure a Jackson objectMapper
instance and inject it into the Spring Context.
In a Spring Managed component we can use Spring’s dependency injection to obtain a reference to the Spring Boot configured objectMapper
. The Jackson objectMapper is thread safe, so we can use the Spring Managed instance throughout our application.
For Spring Boot applications, I prefer to use the Spring managed objectMapper for convenience, consistency, and performance. You’ll also find it handy generating JSON payloads when testing Spring controllers.
For example, if I have the following Spring MVC Controller:
@RestController
public class AuthorController {
@PostMapping("/api/v1/author")
public Author saveAuthor(@RequestBody Author author){
author.setId(UUID.randomUUID().toString());
return author;
}
}
I can write a Spring MockMVC test as follows:
@SpringBootTest
class AuthorControllerTest {
@Autowired
public ObjectMapper objectMapper;
@Autowired
public WebApplicationContext wac;
public MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders
.webAppContextSetup(wac)
.build();
}
@Test
void testPost() throws Exception {
String authorJson = objectMapper
.writeValueAsString(Author
.builder().firstName("John").lastName("Doe").build());
mockMvc.perform(post("/api/v1/author")
.contentType(MediaType.APPLICATION_JSON)
.content(authorJson))
.andExpect(status().isOk());
}
}
In the above example, you can see I’m able to Autowire the objectMapper
into the test, just like any other Spring managed component.
Jackson Annotations
Jackson also has a number of annotations which control serialization and deserialization. You can read about them in detail in this post.
Best Practices for Using the Jackson ObjectMapper
- Reuse the
ObjectMapper
instance:ObjectMapper
is thread-safe after configuration, so it’s best to reuse the instance rather than creating a new one every time. This improves performance by avoiding redundant setup operations. - Handle exceptions carefully: Jackson can throw checked exceptions (e.g.,
JsonProcessingException
) during serialization or deserialization. Make sure to handle these gracefully in your application. - Use proper annotations: Always leverage Jackson’s annotations like
@JsonIgnore
and@JsonProperty
to ensure that the JSON structure matches your API’s requirements. - Use the Spring Managed Object Mapper: When using Spring Boot, use the Spring managed
objectMapper
. Spring provides a number of properties you can use to configure the Spring Managed objectMapper.
Conclusion
Jackson’s ObjectMapper
is a powerful and flexible tool for handling JSON in Java applications. From basic serialization and deserialization to more advanced configurations, Jackson has the flexibility to fit most use cases. By following best practices like reusing the ObjectMapper
instance and leveraging annotations, you can ensure that your application efficiently handles JSON data.
Whether you’re just getting started or have been using Jackson for years, keeping these tips in mind will help you make the most of this robust library.
The source code for this post is available in my GitHub repository here.
Spring Framework 6: Beginner to Guru
Checkout my best selling course on Spring Framework 6. This is the most comprehensive course you will find on Udemy. All things Spring!