Jacksonを使用した動的JSONオブジェクトのマッピング

1. 前書き

Jacksonで定義済みのJSONデータ構造を使用するのは簡単です。 ただし、不明なプロパティを持つ動的な** JSONオブジェクトを処理する必要がある場合があります*。
この短いチュートリアルでは、動的JSONオブジェクトをJavaクラスにマッピングする複数の方法を紹介します。
すべてのテストで、タイプ_com.fasterxml.jackson.databind.ObjectMapper_のフィールド_objectMapper_があると仮定していることに注意してください。

参考文献:

link:/jackson-nested-values [ジャクソンとネストされた値のマッピング]

Jacksonライブラリを使用して、JavaでネストされたJSON値を逆シリアル化する3つの方法を学びます。
link:/jackson-nested-values [詳細]↠’

JacksonでOptionalを使用

JacksonでOptionalを使用する方法の概要。
link:/jackson-optional [続きを読む]↠’

2. _JsonNode_の使用

Webショップで製品仕様を処理したいとします。 *すべての製品には共通の特性がありますが、製品のタイプに依存する特性もあります。*
たとえば、携帯電話のディスプレイのアスペクト比を知りたいのですが、このプロパティは靴にはあまり意味がありません。
データ構造は次のようになります。
{
    "name": "Pear yPhone 72",
    "category": "cellphone",
    "details": {
        "displayAspectRatio": "97:3",
        "audioConnector": "none"
    }
}
_details_オブジェクトに動的プロパティを保存します。
次のJavaクラスを使用して、共通のプロパティをマップできます。
class Product {

    String name;
    String category;

    // standard getters and setters
}
さらに、_details_オブジェクトに適切な表現が必要です。 たとえば、* _ com.fasterxml.jackson.databind.JsonNode_は動的キーを処理できます*。
使用するには、_Product_クラスのフィールドとして追加する必要があります。
class Product {

    // common fields

    JsonNode details;

    // standard getters and setters
}
最後に、機能することを確認します。
String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector").asText()).isEqualTo("none");
ただし、このソリューションには問題があります。 * _JsonNode_フィールドがあるため、クラスはJacksonライブラリに依存します。*

3. _Map_の使用

_details_フィールドに_java.util.Map_を使用することにより、この問題を解決できます。 より正確には、_Map <String、Object> _を使用する必要があります。
他のすべては同じままです。
class Product {

    // common fields

    Map<String, Object> details;

    // standard getters and setters
}
そして、テストでそれを検証できます:
String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

4. _ @ JsonAnySetter_の使用

オブジェクトに動的プロパティのみが含まれる場合、前述のソリューションは有効です。 ただし、1つのJSONオブジェクトに*固定された動的プロパティが混在している場合があります*。
たとえば、製品表現を平坦化する必要がある場合があります。
{
    "name": "Pear yPhone 72",
    "category": "cellphone",
    "displayAspectRatio": "97:3",
    "audioConnector": "none"
}
このような構造を動的オブジェクトとして扱うことができます。 残念ながら、これは共通のプロパティを定義できないことを意味します。それらも動的に処理する必要があります。
または、* _ @ JsonAnySetter_を使用して、追加の不明なプロパティを処理するメソッドをマークできます*。 このようなメソッドは、プロパティの名前と値の2つの引数を受け入れる必要があります。
class Product {

    // common fields

    Map<String, Object> details = new LinkedHashMap<>();

    @JsonAnySetter
    void setDetail(String key, Object value) {
        details.put(key, value);
    }

    // standard getters and setters
}
_NullPointerExceptions_を避けるために、_details_オブジェクトをインスタンス化する必要があることに注意してください。
動的プロパティは_Map_に保存するため、以前と同じように使用できます。
String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

5. カスタムデシリアライザーの作成

ほとんどの場合、これらのソリューションは問題なく機能します。 ただし、より多くの制御が必要な場合があります。 たとえば、JSONオブジェクトに関する逆シリアル化情報をデータベースに保存できます。
カスタムデシリアライザーを使用して、これらの状況をターゲットにできます。 複雑なトピックなので、別の記事で取り上げます。link:/jackson-deserialization[Getting Started with Custom Deserialization in Jackson]。

6. 結論

この記事では、動的JSONオブジェクトをJacksonで処理する複数の方法を見ました。
いつものように、例はhttps://github.com/eugenp/tutorials/tree/master/jackson[GitHubで]から入手できます。