Model Polymorphic JSON with @JsonTypeInfo

Use Jackson's @JsonTypeInfo and @JsonSubTypes to deserialize JSON that includes a "type" discriminator field into the correct concrete subclass.

Special Cases

Detailed Explanation

Polymorphism in JSON

Some APIs return objects whose concrete shape depends on a discriminator field. Jackson's @JsonTypeInfo and @JsonSubTypes annotations let you map a single JSON document to one of several Java subclasses.

Example JSON

{
  "type": "credit_card",
  "last4": "4242",
  "brand": "visa"
}
{
  "type": "bank_account",
  "account_number": "****6789",
  "routing_number": "021000021"
}

Java Hierarchy

import com.fasterxml.jackson.annotation.*;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = CreditCard.class, name = "credit_card"),
    @JsonSubTypes.Type(value = BankAccount.class, name = "bank_account")
})
public abstract class PaymentMethod {
    // common fields, e.g., id, customer_id
}

public class CreditCard extends PaymentMethod {
    private String last4;
    private String brand;
}

public class BankAccount extends PaymentMethod {
    @JsonProperty("account_number")
    private String accountNumber;
    @JsonProperty("routing_number")
    private String routingNumber;
}

How the Generator Handles This

The auto-generator emits a single concrete class per input JSON because a single document does not reveal the polymorphism. After generation:

  1. Identify the discriminator field (often type, kind, or object)
  2. Extract the common fields into an abstract base class
  3. Move the variant-specific fields into subclasses
  4. Add @JsonTypeInfo and @JsonSubTypes annotations

Other Discriminator Strategies

  • include = As.WRAPPER_OBJECT — wrap the payload in an outer object whose key is the type name
  • include = As.EXISTING_PROPERTY — use a discriminator field that already exists on every subclass
  • use = JsonTypeInfo.Id.CLASS — encode the fully qualified Java class name (avoid for public APIs)

Sealed Hierarchies (Java 17+)

Java 17 added sealed classes, which restrict who can extend an abstract type:

public sealed abstract class PaymentMethod permits CreditCard, BankAccount {}

Combined with @JsonSubTypes, sealed classes give you exhaustive pattern matching in the consumer:

String summary = switch (method) {
    case CreditCard cc -> "Card ending " + cc.getLast4();
    case BankAccount ba -> "Bank " + ba.getRoutingNumber();
};

Use Case

Payment APIs (Stripe payment_method.type), event streams (CloudEvents type field), webhook envelopes (event_type), and notification systems all return polymorphic payloads. Modeling them as a Jackson type hierarchy prevents instanceof chains in the consumer.

Try It — JSON to Java

Open full tool