1. 概述

在处理 JSON 时,常见的场景是将一个模型转换成另一个模型。例如,我们可能想要解析一个复杂且嵌套层次深的对象图,将其转换为另一个领域中使用的更简单模型。

在这个快速教程中,我们将学习如何使用 Jackson 将嵌套值扁平化,以简化复杂的数据结构。我们将通过三种不同的方式反序列化 JSON:

  • 使用 @JsonProperty
  • 使用 JsonNode
  • 使用自定义 JsonDeserializer

2. Maven 依赖

首先,让我们在 pom.xml 中添加以下依赖:

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

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

3. JSON 源

考虑以下 JSON 作为我们的示例源:

虽然结构人为设计,但请注意,我们包含了嵌套两层的属性:

{
    "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 类描述的扁平化领域模型中,我们将提取嵌套一层的 brandName 属性,并提取嵌套两层且位于嵌套 brand 对象内的 ownerName 属性:

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 属性。

我们可以指示 Jackson 使用 @JsonProperty 和一些自定义逻辑来拆包嵌套属性,这添加到了我们的 Product 类中:

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");
    }
}

现在,客户端代码可以使用 ObjectMapper 将源 JSON(存储在测试类中的 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. 使用 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<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. 手动注册 Deserializer

为了手动注册自定义 deserializer,客户端代码必须将 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. 自动注册 Deserializer

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

@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 解析包含嵌套值的 JSON 的几种方法。更多示例请参考我们的Jackson 主要教程页面。

如往常一样,代码片段可在 GitHub 查看。