Map<String, Object> で動的な JSON キーをモデリングする

JSON が任意のキー(フィーチャーフラグ、メタデータ、動的設定など)を持つ場合は、クラスを生成せず Map<String, Object> としてモデリングします。

Special Cases

詳細な説明

POJO が向かないケース

ほとんどの JSON は固定スキーマ — キーは事前にわかっており、各キーに文書化された意味があります。一方でフィーチャーフラグ名、ユーザー定義メタデータ、任意タグのように、キー自体がデータである JSON も存在します。そうしたケースでは、各キーを Java フィールドにモデリングするのは不可能です。代わりに Map<String, Object> を使います。

動的キーを含む JSON の例

{
  "user_id": 42,
  "feature_flags": {
    "new_dashboard": true,
    "beta_search": false,
    "ai_assistant": true
  }
}

推奨される Java モデル

package com.example.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;

public class UserContext {
    @JsonProperty("user_id")
    private Integer userId;

    @JsonProperty("feature_flags")
    private Map<String, Boolean> featureFlags;

    // アクセサ
}

値の型が揃っているなら(ここではすべて boolean)型安全のため Map<String, Boolean> を、混在しているなら Map<String, Object> を使います。

ジェネレーターが自動判定できない理由

ジェネレーターは JSON オブジェクトを見てクラスを生成します。キーがスキーマの一部(firstNamelastName)なのかデータの一部(new_dashboardbeta_search)なのかはわかりません。ドメイン知識でキーが動的だとわかっている場合は、生成されたクラスを Map フィールドに置き換えてください。

「Map 形状」の JSON を見分けるヒューリスティック

  • キーがユーザーが作成しそうな識別子(UUID、スラッグ、自由テキスト)に見える
  • 値がすべて同じプリミティブ型
  • ドキュメント間でキーの数が変わる
  • ドキュメントが「メタデータ」「タグ」「カスタムフィールド」と説明している

Jackson の挙動

Jackson は Map<String, T> をネイティブに扱うため、フィールド型以外のアノテーションは不要です。デシリアライザは各 JSON キーを Map エントリとして読み込み、値を T にデコードします。

スキーマと Map の混在

{
  "id": 1,
  "name": "Acme",
  "tags": ["startup", "fintech"],
  "metadata": {
    "founded_year": 2018,
    "headcount": 42,
    "location": "Berlin"
  }
}
public class Company {
    private Integer id;
    private String name;
    private List<String> tags;
    private Map<String, Object> metadata; // 形状不定
}

既知のスキーマ + 拡張用のオープンな metadata バッグ、というよくあるパターンです。

ユースケース

フィーチャーフラグシステム、ユーザー設定、CRM コンタクトのカスタム属性、拡張可能なメタデータを持つ Webhook イベントペイロードなどでは、生成クラスより Map<String, Object> フィールドが向いています。

試してみる — JSON to Java

フルツールを開く