September 6, 2024
java polymorphic json deserialization with jackson

Polymorphic Deserialization In Spring Boot API With Jackson’s JsonTypeInfo And JsonSubTypes

Jackson’s polymorphic deserialization is an exceptional feature that facilitates the deserialization of JSON into a hierarchy of Java objects, particularly when the object type is unknown at compile time. Consider an order validation API that aims to validate various types of orders (e.g., Analysis Order, Repair Order, Replacement Order). Each order type in our application is defined as a subclass of abstract Order class with a dedicated validate method. Also, we know that adhering to the SOLID Principle as always should be fundamental aspect of our development, to have a maintainable clean code which is easy to scale :

  • Open/Closed principle: If we need to add a new order type to the system, it should be done without any modification in the existing code. In other word, adding the new Order subclass with a dedicated validate method to the code, should be the only thing we should do.
  • Liskov substitution principle: The validation API must be able to validate all existing and future subclasses of Order class correctly and also without requiring any modifications to the API code.

In this post, we will learn how the JsonTypeInfo and JsonSubTypes annotations in Java can help us to accomplish the above task.

First, we define the abstract Order class with all basic fields required in an order.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = AnalysisOrder.class, name = "ANALYSIS"),
        @JsonSubTypes.Type(value = RepairOrder.class, name = "REPAIR"),
        @JsonSubTypes.Type(value = ReplacementOrder.class, name = "REPLACEMENT")
})
public abstract class Order {
 
 @JsonProperty("type") 
 protected String type; 

 @JsonProperty("department") 
 protected String department;
 
 @JsonProperty("start_date") 
 protected Date startDate;
 
 @JsonProperty("end_date") 
 protected Date endDate; 

 @JsonProperty("currency") 
 protected String currency;

 protected ValidationResult basicValidate(){
         valid = true;
         validationErrors = new ArrayList<>();
         this.validateDepartment();
         this.validateStartDate();
         this.validateEndDate();
         this.validateCurrency();
         this.validateCost();
    return new ValidationResult(valid, validationErrors);
 }

 // Other methods of the class

}

In the above code, the Order class is annotated with @JsonTypeInfo, specifying that the type field should be used to determine the subclass during deserialization. In addition, the @JsonTypeInfo annotation allows for runtime inclusion of type information in serialized JSON. The @JsonSubTypes annotation defines the possible subtypes that can be deserialized based on the type field. This way, new order types can be introduced by simply creating new child classes inheriting Order and annotating them with @JsonSubTypes.

Then we add sub classes as following:


import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import lombok.experimental.Accessors;
import mst.example.ordervalidation.models.Part;
import mst.example.ordervalidation.models.ValidationError;
import mst.example.ordervalidation.models.ValidationResult;

import java.util.Date;


@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Getter
@Setter
@Accessors(fluent = false, chain = true)
public class RepairOrder extends Order{

    @JsonProperty("analysis_date")
    private Date analysisDate;

    @JsonProperty("test_date")
    private Date testDate;

    @JsonProperty("responsible_person")
    private String responsiblePerson;



    /**
     * This method applies validation related to Repair type of the order
     */
    @Override
    public ValidationResult validate() {

        this.basicValidate();
        this.validateAnalysisDate();
        this.validateResponsiblePerson();
        this.validateTestDate();
        this.validateParts();

        return new ValidationResult(valid, validationErrors);
    }


   // Other methods of the class

}

During deserialization, the type information is used to create the right subclass instance. This allows for seamless conversion between JSON and object of Order sub classes . Also when serializing an Order object to JSON, the output will include a type property indicating the specific subclass. For instance, an AnalysisOrder will be serialized as:

{
  "type": "ANALYSIS",
  "department": "DepartmentA",
  "start_date": "2023-08-01",
  "end_date": "2023-08-10",
  "currency": "USD",
  "cost": 150.0,
  "parts": []
}

In the following code, we implement a POST endpoint /validateOrder accepting a JSON request body representing an Order object . Thanks to Jackson’s annotations, the incoming order is automatically cast to its exact subclass (e.g., AnalysisOrder, RepairOrder, …) based on the type property :

     @PostMapping(value = "/validateOrder", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<ValidationApiResponseModel> validateOrder(@RequestBody Order order) {

     ...

        // Validate the given order
        // The order automatically cast to its exact subclass, so don't need to cast it explicitly
        ValidationResult validationResult = orderValidationService.validateOrder(order);
        
...
        
    }

Then, the order is validated using orderValidationService.validateOrder function which simply calls given order’s validate method. This way, we don’t need to care about the type of order that is being validated, each order subclass has its own validate method that checks parameters specific to that subclass.

You can find complete sample project on my github repository : example for polymorphic deserialization in spring boot

Conclusion

Using JsonTypeInfo and JsonSubTypes annotations facilitates polymorphic deserialization of JSON into a hierarchy of Java objects, particularly when the specific type isn’t known at compile time. It helps us to have a cleaner code, better maintainability, and easy to extent features.

One thought on “Polymorphic Deserialization In Spring Boot API With Jackson’s JsonTypeInfo And JsonSubTypes

  1. Great article! The clear examples make it easy to understand how to handle types dynamically using Jackson. Thanks for sharing!

Leave a Reply

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