ジャクソンによるネストされた値のマッピング
1. 概要
JSONを使用する際の一般的な使用例は、あるモデルから別のモデルへの変換を実行することです。 たとえば、別のドメインで使用するために、複雑で密にネストされたオブジェクトグラフをより簡単なモデルに解析したい場合があります。
この簡単な記事では、複雑なデータ構造を平坦化するためにhow to map nested values with Jacksonを見ていきます。 JSONを次の3つの方法で逆シリアル化します。
-
@JsonPropertyの使用
-
JsonNodeの使用
-
カスタムJsonDeserializerの使用
参考文献:
2. メーベン依存
まず、次の依存関係をpom.xmlに追加しましょう。
com.fasterxml.jackson.core
jackson-databind
2.9.4
Maven Centralでjackson-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でマッピングするには、もう少し作業が必要です。 ここでは、ObjectMapperのreadTreeを使用して、目的のフィールドを解析します。
@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. デシリアライザーの手動登録
カスタムデシリアライザーを手動で登録するには、クライアントコードでJsonDeserializerをModuleに追加し、ModuleをObjectMapperに登録して、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で見つけることができます。