動(dòng)態(tài)JSON怎么處理?Spring Boot五種強(qiáng)大的處理方式
環(huán)境:SpringBoot3.4.2
1. 簡(jiǎn)介
在Web應(yīng)用開發(fā)中,面對(duì)日益復(fù)雜的業(yè)務(wù)場(chǎng)景,客戶端傳遞的JSON數(shù)據(jù)可能并不是固定結(jié)構(gòu),如可變字段、嵌套對(duì)象或用戶自定義配置。
首先,我們來看如下的場(chǎng)景:
public class Person {
private String name ;
private Integer age ;
// getters, setters
}
@PostMapping("/create")
public Person queryUser(@RequestBody Person person) {
return person ;
}當(dāng)我們通過如下的請(qǐng)求body訪問時(shí):
圖片
控制臺(tái)輸出
圖片
未知的email字段。
注:該錯(cuò)誤在當(dāng)前我使用的Spring Boot下是需要開啟如下配置才會(huì)出現(xiàn)上面的錯(cuò)誤
spring:
jackson:
deserialization:
fail-on-unknown-properties: true傳統(tǒng)固定DTO難以滿足此類需求,因此需要API具備處理動(dòng)態(tài)JSON Body的能力。Spring Boot憑借其靈活的請(qǐng)求處理機(jī)制,為我們提供了多種應(yīng)對(duì)方案,能夠有效支持結(jié)構(gòu)不固定的請(qǐng)求數(shù)據(jù),從而提升接口的靈活性和復(fù)用性。
本文將介紹在Spring Boot中處理動(dòng)態(tài)JSON Body的5種常用方式。
2.實(shí)戰(zhàn)案例
2.1 使用Map集合
使用 Map<String, Object> 接收動(dòng)態(tài)JSON Body,可靈活處理結(jié)構(gòu)不確定的請(qǐng)求數(shù)據(jù)。Spring Boot自動(dòng)將JSON鍵值對(duì)映射為Map條目,適用于字段可變、嵌套復(fù)雜或用戶自定義的場(chǎng)景,結(jié)合業(yè)務(wù)邏輯動(dòng)態(tài)解析,提升接口適應(yīng)性與擴(kuò)展性。
@PostMapping("/way1")
public ResponseEntity<?> way1(@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(body) ;
}
圖片
? 優(yōu)點(diǎn):靈活,支持任意字段;無需預(yù)定義DTO;易于動(dòng)態(tài)處理。
? 缺點(diǎn):類型不安全;易出錯(cuò);缺乏結(jié)構(gòu)校驗(yàn);代碼可讀性差。
2.2 使用JsonNode類型
使用 JsonNode 可精確解析任意結(jié)構(gòu)的動(dòng)態(tài)JSON Body。作為Jackson提供的樹模型,它支持遍歷、查詢和類型判斷,適合復(fù)雜嵌套或需深度操作的場(chǎng)景,結(jié)合ObjectMapper靈活處理,是處理不確定JSON的強(qiáng)力方案。
@PostMapping("/way2")
public ResponseEntity<?> createUser(@RequestBody JsonNode body) {
System.err.printf("解析email字段: %s%n", body.get("email").asText()) ;
return ResponseEntity.ok(body) ;
}輸出結(jié)果
圖片
解析email字段: pack@gmail.com? 優(yōu)點(diǎn):結(jié)構(gòu)靈活,支持復(fù)雜嵌套;可精確訪問任意節(jié)點(diǎn);無需定義實(shí)體類。
? 缺點(diǎn):處理深層結(jié)構(gòu)時(shí)代碼冗長。
2.3 DTO + 動(dòng)態(tài)字段(Map)
有時(shí)你已知 80% 的字段,但需要允許額外字段。這時(shí)候我們可以通過 Jackson 注解讓 Person 類更具靈活性。
修改實(shí)體Person:
public class Person {
private String name ;
private Integer age ;
private Map<String, Object> extra = new HashMap<>();
@JsonAnySetter
public void setExtra(String key, Object value) {
extra.put(key, value);
}
@JsonAnyGetter
public Map<String, Object> getExtra() {
return extra;
}
}Controller接口
@PostMapping("/way3")
public ResponseEntity<?> way3(@RequestBody Person person) {
System.err.println(person.getExtra()) ;
return ResponseEntity.ok(person) ;
}輸出結(jié)果
圖片
{email=pack@gmail.com}? 優(yōu)點(diǎn):強(qiáng)類型 + 靈活性;Jackson自動(dòng)捕獲未知字段
? 缺點(diǎn):模型稍顯復(fù)雜
2.4 使用ObjectNode
ObjectNode 是 JsonNode 的可變子類,適合處理動(dòng)態(tài)JSON Body。它提供增刪改查方法,可靈活構(gòu)建和修改JSON結(jié)構(gòu),結(jié)合 ObjectMapper 使用,適用于需動(dòng)態(tài)生成或修改JSON的場(chǎng)景,操作直觀且功能強(qiáng)大。
@PostMapping("/way4")
public ResponseEntity<?> way4(@RequestBody ObjectNode body) {
body.set("author", new TextNode("pack_xg")) ;
body.set("age", new IntNode(34)) ;
return ResponseEntity.ok(body) ;
}這里我們添加author字段,同時(shí)修改了age字段。
圖片
? 優(yōu)點(diǎn):可動(dòng)態(tài)增刪改JSON;操作靈活;適合構(gòu)建和修改結(jié)構(gòu)。
? 缺點(diǎn):需要手動(dòng)操作邏輯。
2.5 動(dòng)態(tài)Json驗(yàn)證
動(dòng)態(tài)JSON Body因結(jié)構(gòu)不固定,傳統(tǒng)基于DTO的注解驗(yàn)證(如@NotNull)難以直接應(yīng)用。當(dāng)使用Map、JsonNode等接收數(shù)據(jù)時(shí),字段名和層級(jí)不確定,導(dǎo)致無法在編解碼階段自動(dòng)校驗(yàn)。這時(shí)候我們可以通過JSON Schema驗(yàn)證這種動(dòng)態(tài)的JSON數(shù)據(jù)。
首先,引入如下依賴
<!--該依賴用來生成要驗(yàn)證的json schema文件-->
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-generator</artifactId>
<version>4.38.0</version>
</dependency>
<!--該依賴用來校驗(yàn)json數(shù)據(jù)-->
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.5.6</version>
</dependency>接下來,我們針對(duì)Person對(duì)象生成對(duì)應(yīng)的Schema
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON) ;
// 設(shè)置必須的字段
configBuilder.forFields()
.withRequiredCheck(field -> "name".equals(field.getName()))
.withRequiredCheck(field -> "age".equals(field.getName())) ;
// 構(gòu)建SchemaGeneratorConfig
SchemaGeneratorConfig config = configBuilder.build();
// 創(chuàng)建SchemaGenerator實(shí)例
SchemaGenerator generator = new SchemaGenerator(config);
// 根據(jù)User類生成Schema內(nèi)容
JsonNode jsonSchema = generator.generateSchema(targetType);
// 將JsonNode轉(zhuǎn)換為ObjectNode以便修改
ObjectNode schemaObject = (ObjectNode) jsonSchema;
// 設(shè)置additionalProperties為false,禁止額外屬性
schemaObject.put("additionalProperties", true);
// 打印生成的JSON Schema
return jsonSchema.toPrettyString() ;內(nèi)容如下:
圖片
最后,我們?cè)贑ontroller接口中進(jìn)行驗(yàn)證
@PostMapping("/validate")
public ResponseEntity<?> validate(@RequestBody ObjectNode body) throws Exception {
InputStream is;
try {
is = new ClassPathResource("schemas/person.json").getInputStream();
} catch (IOException e) {
throw new RuntimeException(e) ;
}
JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012).getSchema(is) ;
List<String> ret = schema.validate(body).stream().map(msg -> msg.getMessage()).toList() ;
return ResponseEntity.ok(ret) ;
}驗(yàn)證結(jié)果

關(guān)于這里的錯(cuò)誤提示內(nèi)容,我們可以自定義 jsv-messages_zh_CN.properties國際化資源文件。




























