Mapping Nested Values with Jackson – 用Jackson映射嵌套值

最后修改: 2017年 10月 25日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

A typical use case when working with JSON is to perform a transformation from one model into another. For example, we might want to parse a complex, densely nested object graph into a more straightforward model for use in another domain.

在使用JSON时,一个典型的用例是将一个模型转换为另一个模型。例如,我们可能想把一个复杂的、密集嵌套的对象图解析成一个更简单的模型,以便在另一个领域使用。

In this quick tutorial, we’ll look at how to map nested values with Jackson to flatten out a complex data structure. We’ll deserialize JSON in three different ways:

在这个快速教程中,我们将看看如何用Jackson映射嵌套的值来平整复杂的数据结构。我们将以三种不同的方式对JSON进行反序列化。

  • Using@JsonProperty
  • Using JsonNode
  • Using a custom JsonDeserializer

2. Maven Dependency

2.Maven的依赖性

Let’s first add the following dependency to pom.xml:

让我们首先将以下依赖关系添加到pom.xml

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>

We can find the latest versions of jackson-databind on Maven Central.

我们可以在Maven Central上找到jackson-databind的最新版本。

3. JSON Source

3.JSON源

Consider the following JSON as the source material for our examples.

考虑将下面的JSON作为我们例子的源材料。

While the structure is contrived, note that we include properties that are nested two levels deep:

虽然结构是设计好的,但请注意,我们包括嵌套在两层深处的属性。

{
    "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. Simplified Domain Model

4.简化的领域模型

In a flattened domain model described by the Product class below, we’ll extract brandName, which is nested one level deep within our source JSON.

在下面的产品类所描述的扁平化领域模型中,我们将提取品牌名称,它被嵌套在我们的源JSON中的一个层次深处。

Also, we’ll extract ownerName, which is nested two levels deep and within the nested brand object:

另外,我们将提取ownerName,它被嵌套在两层深的嵌套brand对象中。

public class Product {

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

    // standard getters and setters
}

5. Mapping With Annotations

5.使用注释的映射

To map the nested brandName property, we first need to unpack the nested brand object to a Map and extract the name property. To map ownerName, we unpack the nested owner object to a Map and extract its name property.

要映射嵌套的brandName属性,我们首先需要将嵌套的brand对象解压到一个Map,并提取name属性。为了映射ownerName,我们将嵌套的owner对象解压到一个Map,并提取其name属性。

We can instruct Jackson to unpack the nested property by using a combination of @JsonProperty and some custom logic that we add to our Product class:

我们可以通过使用@JsonProperty和一些我们添加到Product类中的自定义逻辑的组合,来指示Jackson解压嵌套属性。

public class Product {
    // ...

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

Our client code can now use an ObjectMapper to transform our source JSON, which exists as the String constant SOURCE_JSON within the test class:

我们的客户端代码现在可以使用ObjectMapper来转换我们的源JSON,它作为String常数SOURCE_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. Mapping With JsonNode

6.用JsonNode映射

Mapping a nested data structure with JsonNode requires a little more work.

JsonNode映射一个嵌套的数据结构需要多做一些工作。

Here we use ObjectMapper‘s readTree to parse out the desired fields:

这里我们使用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. Mapping With Custom JsonDeserializer

7.使用自定义JsonDeserializer的映射

Mapping a nested data structure with a custom JsonDeserializer is identical to the JsonNode approach from an implementation point of view.

从实现的角度来看,用自定义的JsonDeserializer映射一个嵌套的数据结构与JsonNode方法是相同的。

We first create the JsonDeserializer:

我们首先创建JsonDeserializer

public class ProductDeserializer extends StdDeserializer<Product> {

    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. Manual Registration of Deserializer

7.1.手动注册反序列化器

To manually register our custom deserializer, our client code must add the JsonDeserializer to a Module, register the Module with an ObjectMapper and call readValue:

要手动注册我们的自定义反序列化器,我们的客户代码必须将JsonDeserializer添加到Module,将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. Automatic Registration of Deserializer

7.2.反序列化器的自动注册

As an alternative to the manual registration of the JsonDeserializer, we can register the deserializer directly on the class:

作为手动注册JsonDeserializer的替代方法,我们可以直接在类上注册解序列器

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

With this approach, there is no need to register manually.

采用这种方法,不需要手动注册。

Let’s take a look at our client code using automatic registration:

让我们来看看我们的客户代码,使用自动注册。

@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. Conclusion

8.结论

In this article, we demonstrated several ways of using Jackson to parse JSON containing nested values. Have a look at our main Jackson Tutorial page for more examples.

在这篇文章中,我们演示了使用Jackson解析包含嵌套值的JSON的几种方法。请看我们的主Jackson教程页面,了解更多的例子。

And, as always, code snippets can be found over on GitHub.

而且,像往常一样,可以在GitHub上找到代码片段