動態 JSON 字段太頭疼?SpringBoot + Jackson 兩個注解輕松搞定序列化!
在開發 Web 接口時,經常會碰到一種情況:前端或第三方返回的 JSON 字段是動態變化的,有些字段你一開始知道,有些字段卻是根據用戶輸入、配置項或業務狀態“臨時冒出來”的。
你可能一開始會想到用 Map<String, Object>
來兜底處理這些字段,但這種方式會讓數據結構不清晰、可維護性差。 其實 Jackson 已經為我們準備好了“動態屬性處理器”:@JsonAnySetter
和 @JsonAnyGetter
,它們可以把這些不確定字段收進來、再自動輸出,不影響已有字段邏輯。
Jackson 注解的“萬能抽屜”方案設計
我們可以把處理邏輯想象成一個數據“雜貨鋪”:
- 常規字段:貨架上擺著 name、age、email 等常用品;
- 動態字段:顧客突然問你“有沒有咖喱味的洗發水”?雖然你之前沒準備,但你能靈活處理,收進一個 Map,這就是“萬能抽屜”。
使用注解:
目標 | Jackson 注解 | 功能說明 |
接收未知字段 |
| 反序列化時接收未定義的字段 |
序列化輸出動態字段 |
| 序列化時輸出 Map 中的字段 |
忽略字段 |
| 顯式忽略某些字段 |
項目結構預覽
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── icoderoad/
│ │ └── json/
│ │ └── handler/
│ │ ├── Person.java
│ │ ├── JsonController.java
│ │ └── JsonApplication.java
│ └── resources/
│ ├── templates/
│ │ ├── form.html
│ │ └── result.html
│ └── application.yml
后端核心類
Person.java
— 支持動態字段的模型類
package com.icoderoad.json.handler;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.HashMap;
import java.util.Map;
/**
* JSON 映射模型:既支持固定字段,也支持動態字段擴展。
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
private String name;
private int age;
// 用于存放動態字段
private Map<String, Object> additionalFields = new HashMap<>();
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@JsonAnySetter
public void addField(String key, Object value) {
additionalFields.put(key, value);
}
@JsonAnyGetter
public Map<String, Object> getAdditionalFields() {
return additionalFields;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Spring Boot 控制器:JsonController.java
package com.icoderoad.json.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* Web 控制器:展示表單、處理提交、展示結果。
*/
@Controller
@RequestMapping("/person")
public class JsonController {
@GetMapping("/form")
public String showForm() {
return "form";
}
@PostMapping("/submit")
public String handleSubmit(
@RequestParam String name,
@RequestParam int age,
@RequestParam Map<String, String> allParams,
Model model
) {
Person person = new Person();
person.setName(name);
person.setAge(age);
for (Map.Entry<String, String> entry : allParams.entrySet()) {
String key = entry.getKey();
if (!key.equals("name") && !key.equals("age")) {
person.addField(key, entry.getValue());
}
}
model.addAttribute("person", person);
return "result";
}
}
application.yml 配置
server:
port: 8080
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
mode: HTML
Thymeleaf + Bootstrap 表單頁:form.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>動態字段提交表單</title>
<link rel="stylesheet">
</head>
<body class="p-4">
<div class="container">
<h2>人員信息提交</h2>
<form method="post" action="/person/submit" id="personForm">
<div class="mb-3">
<label class="form-label">姓名</label>
<input type="text" name="name" class="form-control" required/>
</div>
<div class="mb-3">
<label class="form-label">年齡</label>
<input type="number" name="age" class="form-control" required/>
</div>
<hr>
<h4>添加自定義字段</h4>
<div id="dynamicFields"></div>
<button type="button" class="btn btn-outline-secondary mb-3" onclick="addField()">添加字段</button>
<br>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
<script>
let fieldCount = 0;
function addField() {
const container = document.getElementById("dynamicFields");
const div = document.createElement("div");
div.classList.add("mb-2", "row");
div.innerHTML = `
<div class="col">
<input type="text" class="form-control" name="key${fieldCount}" placeholder="字段名" required>
</div>
<div class="col">
<input type="text" class="form-control" name="value${fieldCount}" placeholder="字段值" required>
</div>
`;
container.appendChild(div);
// 將字段名映射成 key=value 的格式
fieldCount++;
document.getElementById("personForm").addEventListener("submit", function (e) {
const allFields = document.querySelectorAll("#dynamicFields .row");
allFields.forEach(row => {
const keyInput = row.querySelector("input[name^='key']");
const valueInput = row.querySelector("input[name^='value']");
if (keyInput && valueInput) {
const hidden = document.createElement("input");
hidden.type = "hidden";
hidden.name = keyInput.value;
hidden.value = valueInput.value;
document.getElementById("personForm").appendChild(hidden);
}
});
}, {once: true});
}
</script>
</body>
</html>
提交結果展示頁:result.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>提交結果</title>
<link rel="stylesheet">
</head>
<body class="p-4">
<div class="container">
<h2>提交結果</h2>
<table class="table table-bordered">
<tr>
<th>字段</th>
<th>值</th>
</tr>
<tr>
<td>姓名</td>
<td th:text="${person.name}">-</td>
</tr>
<tr>
<td>年齡</td>
<td th:text="${person.age}">-</td>
</tr>
<tr th:each="entry : ${person.additionalFields}">
<td th:text="${entry.key}">字段名</td>
<td th:text="${entry.value}">字段值</td>
</tr>
</table>
<a href="/person/form" class="btn btn-secondary">重新提交</a>
</div>
</body>
</html>
運行效果演示
- 啟動項目后,訪問 http://localhost:8080/person/form;
- 填寫姓名、年齡,并通過“添加字段”添加任意鍵值;
- 點擊提交,跳轉到結果頁展示所有字段(包括動態字段);
- 所有數據經由
@JsonAnySetter
和@JsonAnyGetter
自動解析和序列化。
總結:從動態接收到可視化回顯,一站式搞定
通過本文示例,我們完成了一個支持:
- 不確定 JSON 字段結構解析(
@JsonAnySetter
); - 可序列化輸出的動態字段模型(
@JsonAnyGetter
); - Web 表單中動態字段可視化輸入與管理(Thymeleaf + JS);
- 提交后結構化展示所有信息(Bootstrap + 表格)。
如果你正在構建配置平臺、參數模板系統、插件引擎、或者靈活的前后端數據結構模型,本文架構可以直接復用或擴展。