Core Java

Jackson deserialization using a multi-parameter constructor

JSON deserialization is a common requirement in modern applications that interact with APIs and external systems. Handling different object creation patterns efficiently is key to building robust and maintainable Java applications. Let us delve into understanding Jackson deserialization with a multi-parameter constructor.

1. Introduction to Jackson Deserialization

Jackson is one of the most widely used libraries in Java for processing JSON data. It provides powerful features for converting JSON into Java objects (deserialization) and Java objects into JSON (serialization). By default, Jackson relies on a no-argument constructor and setter methods to populate object fields during deserialization. However, in modern Java applications, developers often prefer immutable objects, constructor-based initialization, and concise data models such as records. In such cases, the default approach is not sufficient. Jackson addresses this limitation by supporting annotations like @JsonCreator and @JsonProperty, which allow deserialization using multi-parameter constructors.

1.1 Setup and Dependency Configuration

To get started, you need to include the Jackson Databind dependency in your project. This module provides the ObjectMapper class, which is the core API used for reading and writing JSON. If you are using Maven, add the following dependency to your pom.xml:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>stable__latest__version</version>
</dependency>

Make sure to replace stable__latest__version with the latest stable version available in Maven Central. Once added, your project will have access to all core Jackson features required for JSON deserialization.

2. Complete Code Example

This example demonstrates multiple Jackson deserialization approaches in a single program: default constructor, @JsonCreator with a multi-parameter constructor, Java records, and enum handling.

// Main.java

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

// Enum with custom deserialization
enum Status {
    ACTIVE, INACTIVE;

    @JsonCreator
    public static Status fromValue(String value) {
        return Status.valueOf(value.toUpperCase());
    }
}

// 1. Default deserialization (uses constructor + setters)
class DefaultUser {
    private String name;
    private int age;
    private Status status;

    public DefaultUser() {} // required default constructor

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

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

    public Status getStatus() { return status; }
    public void setStatus(Status status) { this.status = status; }
}

// 2. @JsonCreator with multi-parameter constructor (immutable)
class ImmutableUser {
    private final String name;
    private final int age;

    @JsonCreator
    public ImmutableUser(@JsonProperty("name") String name,
                         @JsonProperty("age") int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
}

// 3. Record (Java 14+)
record RecordUser(String name, int age) {}

public class Main {
    public static void main(String[] args) throws Exception {

        ObjectMapper mapper = new ObjectMapper();

        // Default deserialization
        String json1 = "{ \"name\": \"John\", \"age\": 30, \"status\": \"active\" }";
        DefaultUser user1 = mapper.readValue(json1, DefaultUser.class);
        System.out.println("DefaultUser: " + user1.getName() + " - " + user1.getAge() + " - " + user1.getStatus());

        // @JsonCreator deserialization
        String json2 = "{ \"name\": \"Alice\", \"age\": 25 }";
        ImmutableUser user2 = mapper.readValue(json2, ImmutableUser.class);
        System.out.println("ImmutableUser: " + user2.getName() + " - " + user2.getAge());

        // Record deserialization
        String json3 = "{ \"name\": \"Bob\", \"age\": 40 }";
        RecordUser user3 = mapper.readValue(json3, RecordUser.class);
        System.out.println("RecordUser: " + user3.name() + " - " + user3.age());
    }
}

2.1 Code Explanation

This program demonstrates multiple ways to perform JSON deserialization using Jackson in Java, including default deserialization, constructor-based deserialization with @JsonCreator, record support, and custom enum handling. The ObjectMapper class is the core component responsible for converting JSON strings into Java objects.

  • The Status enum defines two constants: ACTIVE and INACTIVE. It uses a custom @JsonCreator method named fromValue, which allows Jackson to convert incoming JSON values into enum constants. The method transforms the input string to uppercase before mapping it, enabling case-insensitive deserialization (for example, “active” becomes ACTIVE).
  • The DefaultUser class demonstrates standard Jackson deserialization. It contains private fields along with a no-argument constructor and public getter and setter methods. During deserialization, Jackson first creates an instance using the default constructor and then populates the fields using the setter methods. This is the most common and straightforward approach but requires mutable objects.
  • The ImmutableUser class illustrates deserialization using a multi-parameter constructor. Since the fields are marked as final and there are no setters, Jackson cannot use the default approach. Instead, the constructor is annotated with @JsonCreator, and each parameter is annotated with @JsonProperty to map JSON fields to constructor arguments. This approach is ideal for creating immutable objects.
  • The RecordUser is a Java record, which provides a concise way to define immutable data carriers. Records automatically generate a canonical constructor, accessor methods, and other utility methods. Jackson natively supports records and uses the generated constructor for deserialization without requiring additional annotations.
  • In the Main class, an ObjectMapper instance is created to handle the JSON processing. Three JSON strings are defined and deserialized into their respective classes. The first JSON string is mapped to DefaultUser, demonstrating setter-based population. The second is mapped to ImmutableUser, showcasing constructor-based deserialization. The third is mapped to RecordUser, highlighting record support. Finally, the program prints the deserialized values to verify that each approach works as expected.

2.2 Code Output

DefaultUser: John - 30 - ACTIVE
ImmutableUser: Alice - 25
RecordUser: Bob - 40

The output shows the successful deserialization of three different JSON inputs into their respective Java representations using different Jackson techniques. Each line corresponds to a specific deserialization strategy used in the program.

  • In the first case, the JSON string containing name, age, and status is deserialized into the DefaultUser class. Jackson invokes the default constructor and then sets each field using the setter methods. The status value “active” is passed to the enum’s @JsonCreator method, which converts it to uppercase and maps it to ACTIVE.
  • In the second case, the JSON is deserialized into the ImmutableUser class. Since the class does not have a default constructor or setters, Jackson uses the constructor annotated with @JsonCreator. The @JsonProperty annotations ensure that the JSON fields correctly map to the constructor parameters, allowing the object to be created with initialized final fields.
  • In the third case, the JSON is deserialized into the RecordUser record. Jackson automatically detects the record structure and uses its canonical constructor to populate the fields. No additional annotations or configuration are required, making this approach concise and modern.

Overall, the output confirms that Jackson can handle mutable objects, immutable classes with multi-parameter constructors, and Java records seamlessly, while also supporting customized enum deserialization logic.

3. Conclusion

Jackson provides flexible ways to deserialize JSON into Java objects, even when using parameterized constructors. While default deserialization works for mutable objects, @JsonCreator is essential for immutable designs. Modern Java features like records further simplify this process, and enums can be customized for robust parsing. Choosing the right approach depends on your object design and application requirements.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

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

0 Comments
Oldest
Newest Most Voted
Back to top button