Jacksonでネストした値をマッピングする

ジャクソンによるネストされた値のマッピング

1. 概要

JSONを使用する際の一般的な使用例は、あるモデルから別のモデルへの変換を実行することです。 たとえば、別のドメインで使用するために、複雑で密にネストされたオブジェクトグラフをより簡単なモデルに解析したい場合があります。

この簡単な記事では、複雑なデータ構造を平坦化するためにhow to map nested values with Jacksonを見ていきます。 JSONを次の3つの方法で逆シリアル化します。

  • @JsonPropertyの使用

  • JsonNodeの使用

  • カスタムJsonDeserializerの使用

参考文献:

JacksonでOptionalを使用する

JacksonでOptionalを使用する方法の概要。

ジャクソンとの相続

このチュートリアルでは、サブタイプメタデータの包含を処理し、ジャクソンのスーパークラスから継承されたプロパティを無視する方法を示します。

Spring Bootで@JsonComponentを使用する

Spring Bootで@JsonComponentアノテーションを使用する方法を学びます。

2. メーベン依存

まず、次の依存関係をpom.xmlに追加しましょう。


    com.fasterxml.jackson.core
    jackson-databind
    2.9.4

Maven Centraljackson-databindの最新バージョンを見つけることができます。

3. JSONソース

サンプルのソース資料として、次のJSONを検討してください。 構造は不自然ですが、2レベルの深さにネストされたプロパティが含まれていることに注意してください。

{
    "id": "957c43f2-fa2e-42f9-bf75-6e3d5bb6960a",
    "name": "The Best Product",
    "brand": {
        "id": "9bcd817d-0141-42e6-8f04-e5aaab0980b6",
        "name": "ACME Products",
        "owner": {
            "id": "b21a80b1-0c09-4be3-9ebd-ea3653511c13",
            "name": "Ultimate Corp, Inc."
        }
    }
}

4. 簡略化されたドメインモデル

以下のProductクラスで説明されているフラット化されたドメインモデルでは、ソースJSONの1レベル下にネストされているbrandNameを抽出します。 また、ownerNameを抽出します。これは、2レベルの深さで、ネストされたbrandオブジェクト内にネストされています。

public class Product {

    private String id;
    private String name;
    private String brandName;
    private String ownerName;

    // standard getters and setters
}

5. 注釈付きのマッピング

ネストされたbrandNameプロパティをマップするには、最初にネストされたbrandオブジェクトをMapに解凍し、nameプロパティを抽出する必要があります。 次に、ownerNameをマップするために、ネストされたownerオブジェクトをMapに解凍し、そのnameプロパティを抽出します。

Productクラスに追加するunpack the nested property by using a combination of @JsonProperty and some custom logicをJacksonに指示できます。

public class Product {
    // ...

    @SuppressWarnings("unchecked")
    @JsonProperty("brand")
    private void unpackNested(Map brand) {
        this.brandName = (String)brand.get("name");
        Map owner = (Map)brand.get("owner");
        this.ownerName = owner.get("name");
    }
}

これで、クライアントコードはObjectMapperを使用して、テストクラス内にString定数SOURCE_JSONとして存在するソースJSONを変換できます。

@Test
public void whenUsingAnnotations_thenOk() throws IOException {
    Product product = new ObjectMapper()
      .readerFor(Product.class)
      .readValue(SOURCE_JSON);

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

6. JsonNodeによるマッピング

ネストされたデータ構造をJsonNodeでマッピングするには、もう少し作業が必要です。 ここでは、ObjectMapperreadTreeを使用して、目的のフィールドを解析します。

@Test
public void whenUsingJsonNode_thenOk() throws IOException {
    JsonNode productNode = new ObjectMapper().readTree(SOURCE_JSON);

    Product product = new Product();
    product.setId(productNode.get("id").textValue());
    product.setName(productNode.get("name").textValue());
    product.setBrandName(productNode.get("brand")
      .get("name").textValue());
    product.setOwnerName(productNode.get("brand")
      .get("owner").get("name").textValue());

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

7. カスタムJsonDeserializerを使用したマッピング

ネストされたデータ構造をカスタムJsonDeserializerでマッピングすることは、実装の観点からはJsonNodeアプローチと同じです。 最初にJsonDeserializer:を作成します

public class ProductDeserializer extends StdDeserializer {

    public ProductDeserializer() {
        this(null);
    }

    public ProductDeserializer(Class vc) {
        super(vc);
    }

    @Override
    public Product deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {

        JsonNode productNode = jp.getCodec().readTree(jp);
        Product product = new Product();
        product.setId(productNode.get("id").textValue());
        product.setName(productNode.get("name").textValue());
        product.setBrandName(productNode.get("brand")
          .get("name").textValue());
        product.setOwnerName(productNode.get("brand").get("owner")
          .get("name").textValue());
        return product;
    }
}

7.1. デシリアライザーの手動登録

カスタムデシリアライザーを手動で登録するには、クライアントコードでJsonDeserializerModuleに追加し、ModuleObjectMapperに登録して、readValue:を呼び出す必要があります。

@Test
public void whenUsingDeserializerManuallyRegistered_thenOk()
 throws IOException {

    ObjectMapper mapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addDeserializer(Product.class, new ProductDeserializer());
    mapper.registerModule(module);

    Product product = mapper.readValue(SOURCE_JSON, Product.class);

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

7.2. デシリアライザーの自動登録

JsonDeserializer,を手動で登録する代わりに、register the deserializer directly on the classを使用できます。

@JsonDeserialize(using = ProductDeserializer.class)
public class Product {
    // ...
}

このアプローチでは、手動で登録する必要はありません。 自動登録を使用したクライアントコードを見てみましょう。

@Test
public void whenUsingDeserializerAutoRegistered_thenOk()
  throws IOException {

    ObjectMapper mapper = new ObjectMapper();
    Product product = mapper.readValue(SOURCE_JSON, Product.class);

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

8. 結論

このチュートリアルでは、Jackson to parse JSON containing nested valuesを使用するいくつかの方法を示しました。 その他の例については、メインのJackson Tutorialページをご覧ください。

そして、いつものように、コードスニペットはover on GitHubで見つけることができます。