使用 Jackson 映射嵌套值
上次更新:2024 年 1 月 8 日
1. 概述
在使用 JSON 时,一个典型的用例是从一个模型转换到另一个模型。例如,我们可能希望将一个复杂、高度嵌套的对象图解析为更直接的模型,以便在另一个领域中使用。
在本快速教程中,我们将研究如何使用 Jackson 映射嵌套值,以展平复杂的数据结构。我们将以三种不同的方式反序列化 JSON
- 使用@JsonProperty
- 使用 JsonNode
- 使用自定义 JsonDeserializer
更多阅读
2. Maven 依赖
首先,我们将以下依赖项添加到 pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</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,它嵌套在我们的源 JSON 中一层。
此外,我们将提取ownerName,它嵌套在两层深,并在嵌套的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 属性。
我们可以指示 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 转换为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. 使用 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<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. 反序列化器的手动注册
要手动注册我们的自定义反序列化器,我们的客户端代码必须将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 手动注册的替代方案,我们可以直接在类上注册反序列化器
@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 上获取。 一旦你以 Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。















