环境:SpringBoot3.4.0
1. 简介
使用 Jackson 处理预定义的 JSON 数据结构非常简单。Spring Boot 默认使用 Jackson 作为其 JSON 处理库,因此,在默认情况下,我们无需进行任何额外的配置即可轻松地在 Spring Boot 应用中序列化和反序列化 JSON 数据。
然而,有时我们需要处理具有未知属性的动态 JSON 对象,这些对象的结构可能在运行时有所不同,超出了预定义数据结构的范围。在这种情况下,我们就需要采用一些特殊的技术或策略来灵活地处理这些动态 JSON 对象。
1.1 什么是动态属性?
首先,我们有如下的实体类:
public class Product {
private String name;
private String category;
// getters, setters
}
对应的JSON字符串如下:
{
"name": "SpringBoot实战案例100例",
"category": "book",
"details": {
"price": "70",
"author": "XGPack"
}
}
这里的 "details" 代表的是动态属性,它在 Product 实体对象中并没有预先定义的对应属性。
接下来,本篇文章将介绍4种方式处理这种JSON对象中包含动态属性的情况。
2. 实战案例
2.1 使用JsonNode属性
我们可以在 Product 类中添加一个类型为 com.fasterxml.jackson.databind.JsonNode 的属性,用来接收和处理上述的 details 动态属性,如下示例:
public class Product {
// other properties
private JsonNode details;
// getters, setters
}
测试代码
ObjectMapper objectMapper = new ObjectMapper() ;
String json = """
{
"name": "SpringBoot实战案例100例",
"category": "book",
"details": {
"price": "70",
"author": "XGPack"
}
}
""" ;
Product product = objectMapper.readValue(json, Product.class) ;
System.err.printf("name: %s, category: %s%n", product.getName(), product.getCategory()) ;
System.out.println("--------------------------------") ;
System.err.printf("price: %s, auther: %s%n",
product.getDetails().get("price").asText(),
product.getDetails().get("author").asText()) ;
输出结果
图片
问题得到了解决,但这个解决方案存在一个问题;由于我们有一个 JsonNode 字段,我们的类依赖于 Jackson 库。
2.2 使用Map集合
我们还可以使用Map集合来接收这些动态属性,如下示例:
public class Product_Map {
// other properties
private Map<String, Object> details;
// getters, setters
}
测试代码
ObjectMapper objectMapper = ... ;
String json = ... ;
Product product = objectMapper.readValue(json, Product.class) ;
System.err.printf("name: %s, category: %s%n", product.getName(), product.getCategory()) ;
System.out.println("--------------------------------") ;
System.err.printf("price: %s, author: %s%n",
product.getDetails().get("price"),
product.getDetails().get("author")) ;
此种方式有通过JsonNode差不多,只是这种方式不依赖于jackson包。
2.3 使用@JsonAnySetter注解
当对象只包含动态属性时(details),上面的2个解决方案是很好的选择。然而,有时我们在一个 JSON 对象中混合了固定属性和动态属性。也就是如下数据格式时:
{
"name": "SpringBoot实战案例100例",
"category": "book",
"price": "70",
"author": "XGPack"
}
动态属性与固定的属性是平级混合在一起,这种情况我们可以使用 @JsonAnySetter 注解来标记一个方法,以处理额外的、未知的属性。这样的方法应该接受两个参数:属性的key和value。
public class Product {
// other properties
private Map<String, Object> details = new LinkedHashMap<>() ;
@JsonAnySetter
public void setDetail(String key, Object value) {
details.put(key, value) ;
}
// getters, setters
}
这里,我们在 setter 方法上使用 @JsonAnySetter 注解,以便该方法能够处理那些动态的属性。
测试代码
ObjectMapper objectMapper = ... ;
String json = ... ;
Product product = objectMapper.readValue(json, Product.class) ;
System.err.printf("name: %s, category: %s%n", product.getName(), product.getCategory()) ;
System.out.println("--------------------------------") ;
System.err.printf("price: %s, author: %s%n",
product.getDetails().get("price"),
product.getDetails().get("author")) ;
输出结果
图片
2.4 自定义反序列化器
在大多数情况下,这些解决方案都能很好地工作;然而,当我们需要更多的控制时,我们可以使用自定义反序列化器来处理。
public class ProductDeserializer extends StdDeserializer<Product> {
public ProductDeserializer() {
this(null);
}
public ProductDeserializer(Class<?> vc) {
super(vc);
}
@Override
public Product_CustomDeserializer deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp) ;
// 获取通用字段
String name = node.get("name").asText() ;
String category = node.get("category").asText() ;
// 获取动态属性
JsonNode detailsNode = node.get("details");
String price = detailsNode.get("price").asText() ;
String author = detailsNode.get("author").asText() ;
Map<String, Object> details = new HashMap<>() ;
details.put("price", displayAspectRatio) ;
details.put("author", audioConnector) ;
return new Product(name, category, details) ;
}
}
测试代码:
ObjectMapper objectMapper = new ObjectMapper() ;
// 注册反序列获器
SimpleModule module = new SimpleModule();
module.addDeserializer(Product.class, new ProductDeserializer());
objectMapper.registerModule(module) ;
String json = ... ;
// ...
这里通过编程的方式注册自定义反序列化器,其它代码都是一样的。
我们还可以通过更简单的方式进行处理,直接通过注解的方式注册自定义的反序列化器:
@JsonDeserialize(using = ProductDeserializer.class)
public class Product {
// ...
}
在Spring Boot环境下,那么还可以通过如下方式定义和注册反序列化器。
@JsonComponent
public class PackJsonComponent {
// 自定义反序列化
public static class Deserializer extends JsonDeserializer<Product> {
@Override
public Product deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = jp.getCodec().readTree(jp) ;
String name = node.get("name").asText() ;
String category = node.get("category").asText() ;
// ...
return new Product(name, category, details) ;
}
}
}
使用@JsonComponent注解即可。