NS
Naveen Seetharam

Polymorphic JSON with Jackson: Complete Guide to @JsonTypeInfo and @JsonSubTypes

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 JSON
  • include = JsonTypeInfo.As.PROPERTY: Includes type info as a JSON property within the object
  • property = "type": Specifies the property name for type information

3. @JsonSubTypes Mapping Strategy

The @JsonSubTypes annotation maps logical names to concrete classes:

  • name = "circle" maps to Circle.class
  • name = "rectangle" maps to Rectangle.class
  • name = "triangle" maps to Triangle.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

  1. Missing Default Constructor: Ensure all concrete classes have default constructors
  2. Type Property Mismatch: Verify the type property names match exactly
  3. Circular Dependencies: Avoid circular references in object hierarchies
  4. Missing Annotations: Don’t forget @JsonSubTypes on 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.