Spring Boot 優(yōu)雅處理 JSON動態(tài)屬性
環(huán)境:SpringBoot3.4.0
1. 簡介
使用 Jackson 處理預定義的 JSON 數(shù)據結構非常簡單。Spring Boot 默認使用 Jackson 作為其 JSON 處理庫,因此,在默認情況下,我們無需進行任何額外的配置即可輕松地在 Spring Boot 應用中序列化和反序列化 JSON 數(shù)據。
然而,有時我們需要處理具有未知屬性的動態(tài) JSON 對象,這些對象的結構可能在運行時有所不同,超出了預定義數(shù)據結構的范圍。在這種情況下,我們就需要采用一些特殊的技術或策略來靈活地處理這些動態(tài) JSON 對象。
1.1 什么是動態(tài)屬性?
首先,我們有如下的實體類:
public class Product {
private String name;
private String category;
// getters, setters
}
對應的JSON字符串如下:
{
"name": "SpringBoot實戰(zhàn)案例100例",
"category": "book",
"details": {
"price": "70",
"author": "XGPack"
}
}
這里的 "details" 代表的是動態(tài)屬性,它在 Product 實體對象中并沒有預先定義的對應屬性。
接下來,本篇文章將介紹4種方式處理這種JSON對象中包含動態(tài)屬性的情況。
2. 實戰(zhàn)案例
2.1 使用JsonNode屬性
我們可以在 Product 類中添加一個類型為 com.fasterxml.jackson.databind.JsonNode 的屬性,用來接收和處理上述的 details 動態(tài)屬性,如下示例:
public class Product {
// other properties
private JsonNode details;
// getters, setters
}
測試代碼
ObjectMapper objectMapper = new ObjectMapper() ;
String json = """
{
"name": "SpringBoot實戰(zhàn)案例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集合來接收這些動態(tài)屬性,如下示例:
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注解
當對象只包含動態(tài)屬性時(details),上面的2個解決方案是很好的選擇。然而,有時我們在一個 JSON 對象中混合了固定屬性和動態(tài)屬性。也就是如下數(shù)據格式時:
{
"name": "SpringBoot實戰(zhàn)案例100例",
"category": "book",
"price": "70",
"author": "XGPack"
}
動態(tài)屬性與固定的屬性是平級混合在一起,這種情況我們可以使用 @JsonAnySetter 注解來標記一個方法,以處理額外的、未知的屬性。這樣的方法應該接受兩個參數(shù):屬性的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 注解,以便該方法能夠處理那些動態(tài)的屬性。
測試代碼
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 自定義反序列化器
在大多數(shù)情況下,這些解決方案都能很好地工作;然而,當我們需要更多的控制時,我們可以使用自定義反序列化器來處理。
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() ;
// 獲取動態(tài)屬性
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環(huán)境下,那么還可以通過如下方式定義和注冊反序列化器。
@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注解即可。