Polymorphic JSON with Jackson: Complete Guide to @JsonTypeInfo and @JsonSubTypes
Introduction
When building REST APIs with Spring Boot and Jackson, you often encounter scenarios where you must handle different types of objects through a single endpoint. This is where polymorphic JSON capabilities become essential. In this comprehensive guide, we’ll explore how to implement polymorphic JSON serialization and deserialization using Jackson’s @JsonTypeInfo and @JsonSubTypes annotations.
Polymorphic JSON allows you to work with inheritance hierarchies in your JSON data, making it perfect for APIs that need to handle various related object types through unified endpoints.
What is Polymorphic JSON?
Polymorphic JSON enables you to serialize and deserialize objects of different types through a common base class or interface. This pattern is particularly valuable when:
- You have a hierarchy of related classes that share common properties
- You need to handle different object types through a single API endpoint
- You want to maintain type safety while providing flexibility in your JSON structure
- You’re building microservices that exchange varied but related data structures
Project Overview
This demo project showcases polymorphic JSON handling using a Shape hierarchy with three concrete implementations:
- Circle - with radius property
- Rectangle - with width and height properties
- Triangle - with base, height, and side properties
Key Components
1. Base Abstract Class with @JsonTypeInfo
The foundation of polymorphic JSON handling starts with proper annotation configuration:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = Circle.class, name = "circle"),
@JsonSubTypes.Type(value = Rectangle.class, name = "rectangle"),
@JsonSubTypes.Type(value = Triangle.class, name = "triangle")
})
public abstract class Shape {
private String color;
private String name;
// Common methods and properties
public abstract double calculateArea();
public abstract double calculatePerimeter();
}
2. @JsonTypeInfo Configuration Explained
The @JsonTypeInfo annotation controls how type information is included in JSON:
use = JsonTypeInfo.Id.NAME: Uses logical type names instead of full class names for cleaner JSONinclude = JsonTypeInfo.As.PROPERTY: Includes type info as a JSON property within the objectproperty = "type": Specifies the property name for type information
3. @JsonSubTypes Mapping Strategy
The @JsonSubTypes annotation maps logical names to concrete classes:
name = "circle"maps toCircle.classname = "rectangle"maps toRectangle.classname = "triangle"maps toTriangle.class
JSON Examples and Use Cases
Circle JSON Structure
{
"type": "circle",
"name": "My Circle",
"color": "Red",
"radius": 5.0
}
Rectangle JSON Structure
{
"type": "rectangle",
"name": "My Rectangle",
"color": "Blue",
"width": 10.0,
"height": 20.0
}
Triangle JSON Structure
{
"type": "triangle",
"name": "My Triangle",
"color": "Green",
"base": 8.0,
"height": 6.0,
"sideA": 10.0,
"sideB": 10.0
}
Polymorphic Array Example
[
{
"type": "circle",
"name": "Circle1",
"color": "Red",
"radius": 3.0
},
{
"type": "rectangle",
"name": "Rectangle1",
"color": "Blue",
"width": 4.0,
"height": 5.0
},
{
"type": "triangle",
"name": "Triangle1",
"color": "Green",
"base": 6.0,
"height": 4.0,
"sideA": 5.0,
"sideB": 5.0
}
]
Best Practices for Polymorphic JSON
1. Use Logical Type Names
Always use readable, logical names instead of full class names:
@JsonSubTypes.Type(value = Circle.class, name = "circle") // ✅ Good
@JsonSubTypes.Type(value = Circle.class, name = "com.example.Circle") // ❌ Avoid
2. Consistent Property Placement
Place the type property consistently at the beginning of your JSON objects:
@JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, property = "type")
3. Implement Proper Validation
Always validate incoming JSON to ensure the type field is present and valid.
4. Error Handling
Implement comprehensive error handling for unknown types and malformed JSON.
Common Pitfalls to Avoid
- Missing Default Constructor: Ensure all concrete classes have default constructors
- Type Property Mismatch: Verify the type property names match exactly
- Circular Dependencies: Avoid circular references in object hierarchies
- Missing Annotations: Don’t forget
@JsonSubTypeson the base class
Alternative Approaches
Using Class Names
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
Using Custom Properties
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "shapeType")
@JsonTypeIdResolver(CustomTypeIdResolver.class)
Wrapper Objects
@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT)
Conclusion
Jackson’s polymorphic JSON capabilities provide a powerful way to handle complex object hierarchies in REST APIs. By using @JsonTypeInfo and @JsonSubTypes, you can:
- Maintain type safety during serialization/deserialization
- Support multiple object types through a single endpoint
- Keep JSON payloads clean and readable
- Leverage compile-time type checking
- Build more flexible and maintainable APIs
This approach is particularly valuable in microservices architectures where different services need to exchange varied but related data structures.
Complete Working Example
You can find a complete working example of this implementation in the polymorphic-json-demo repository on GitHub.