Master Polymorphic Deserialization in Spring Boot 3 with Jackson
This tutorial shows how to use Jackson's polymorphic deserialization in Spring Boot 3 to handle variable JSON request bodies by defining a common Order interface, concrete implementations, and a REST controller that routes calculations based on the order type, eliminating redundant endpoints.
Introduction
When the JSON body format is unknown, defining a separate API for each possible structure leads to redundant endpoints. Polymorphic deserialization with Jackson provides a concise and extensible solution.
Case Study
Assume an online store needs to handle three order types: regular, discount, and promo, each with different fields and calculation logic.
1. Interface Definition
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "orderType", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = RegularOrder.class, name = "regular"),
@JsonSubTypes.Type(value = DiscountOrder.class, name = "discount"),
@JsonSubTypes.Type(value = PromoOrder.class, name = "promo")
})
public interface Order {
}2. POJO Implementations
public class DiscountOrder implements Order {
private double price;
private int quantity;
private double discountRate;
}
public class PromoOrder implements Order {
private double price;
private int quantity;
private String promoCode;
}
public class RegularOrder implements Order {
private double price;
private int quantity;
}3. Controller
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping("/calculate")
public ResponseEntity<Double> calculateTotal(@RequestBody Order order) {
double total = 0;
switch (order) {
case RegularOrder r -> total = orderService.calculateRegularTotal(r);
case DiscountOrder d -> total = orderService.calculateDiscountTotal(d);
case PromoOrder p -> total = orderService.calculatePromoTotal(p);
default -> logger.error("Unknown order type: {}", order);
}
return ResponseEntity.ok(total);
}
}4. Service
@Service
public class OrderService {
public double calculateRegularTotal(RegularOrder order) {
return order.getPrice() * order.getQuantity();
}
public double calculateDiscountTotal(DiscountOrder order) {
return order.getPrice() * order.getQuantity() * (1 - order.getDiscountRate());
}
public double calculatePromoTotal(PromoOrder order) {
return order.getPrice() * order.getQuantity() * 0.75;
}
}5. Alternative: Dynamic Field Types
By annotating a field with @JsonTypeInfo and @JsonSubTypes, the field can hold different Java types based on a "type" property in the JSON.
public class Order {
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = String.class, name = "string"),
@JsonSubTypes.Type(value = Long.class, name = "long"),
@JsonSubTypes.Type(value = Integer.class, name = "int")
})
protected Object value;
}When the request contains "type":"long", the value field is deserialized as a Long, etc.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
