@JsonTypeInfo でポリモーフィックな JSON をモデリングする

Jackson の @JsonTypeInfo と @JsonSubTypes で、"type" 識別子フィールドを持つ JSON を正しい具象サブクラスにデシリアライズします。

Special Cases

詳細な説明

JSON におけるポリモーフィズム

具体的な形が識別子フィールドに依存するオブジェクトを返す API があります。Jackson の @JsonTypeInfo@JsonSubTypes を使えば、ひとつの JSON を複数の Java サブクラスのどれかにマッピングできます。

JSON の例

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

Java 階層

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 {
    // 共通フィールド(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;
}

自動生成での扱い

単一の JSON ドキュメントだけではポリモーフィズムは判別できないため、自動ジェネレーターは具象クラスを 1 つ生成するに留まります。生成後は次の手順で整理します。

  1. 識別子フィールドを特定する(よくあるのは typekindobject
  2. 共通フィールドを抽象基底クラスに切り出す
  3. バリアント固有のフィールドをサブクラスへ移す
  4. @JsonTypeInfo@JsonSubTypes を付与する

他の識別子戦略

  • include = As.WRAPPER_OBJECT — 型名をキーとする外側オブジェクトでラップする
  • include = As.EXISTING_PROPERTY — 各サブクラスにすでに存在するプロパティを識別子として利用
  • use = JsonTypeInfo.Id.CLASS — 完全修飾 Java クラス名を埋め込む(公開 API では避ける)

Sealed 階層(Java 17+)

Java 17 では sealed クラスにより、誰が抽象型を継承できるかを制限できます。

public sealed abstract class PaymentMethod permits CreditCard, BankAccount {}

@JsonSubTypes と組み合わせると、消費側で網羅的なパターンマッチが可能です。

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

ユースケース

決済 API(Stripe の payment_method.type)、イベントストリーム(CloudEvents の type フィールド)、Webhook エンベロープ(event_type)、通知システムなどはポリモーフィックなペイロードを返します。Jackson の型階層としてモデリングすれば消費側の instanceof チェーンを排除できます。

試してみる — JSON to Java

フルツールを開く