How to The Jackson Object Mapper with JSON

How to The Jackson Object Mapper with JSON

0 Comments

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:

Jackson Object Mapper deserialization to Java Map

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 with System.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 objectMapper 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.

About jt

    You May Also Like

    Leave a Reply

    Your email address will not be published. Required fields are marked *

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