Java 中 JSON 字段不固定怎么搞序列化?用好這兩個(gè)注解就夠了!
最近在處理一個(gè) JSON 接口時(shí),遇到這樣一種情況:返回的數(shù)據(jù)中包含一些我事先并不知道的字段,這些字段會(huì)根據(jù)上下文動(dòng)態(tài)變化,沒辦法在 Java 類中提前寫死字段名。
起初我以為只能通過 Map 手動(dòng)解析,但后來發(fā)現(xiàn) Jackson 提供了 @JsonAnyGetter 和 @JsonAnySetter 這兩個(gè)注解,專門用來處理這種“動(dòng)態(tài)屬性”。它們能讓我優(yōu)雅地把未知字段收集起來或者序列化出去,不影響已知字段的正常處理。
不過我在使用過程中也有點(diǎn)疑惑,比如這兩個(gè)注解的用法順序有什么講究?有哪些坑需要避免?這種方式是不是適合所有動(dòng)態(tài)字段的場景?
開家雜貨鋪吧
@JsonAnyGetter/@JsonAnySetter: 像開了一家靈活應(yīng)對(duì)一切需求的「雜貨鋪」
想象你是個(gè) JSON 雜貨鋪老板,門口寫著招牌:“你有啥,我都能裝;你要啥,我都能配。”
你平時(shí)會(huì)備一些常規(guī)商品(字段),但總有顧客帶些奇怪需求來問:
- “老板,有沒有草莓味的牙膏?”
- “能不能加點(diǎn)冰塊到辣醬里?”
這些你事先沒在貨架上準(zhǔn)備的“臨時(shí)需求”,你也得接單,對(duì)吧?
這時(shí)候你就需要一對(duì)“萬能架子”——也就是:
@JsonAnySetter:隨便放!你給啥我都能接
每次有奇怪字段進(jìn)來(JSON 反序列化時(shí)),你就把它們統(tǒng)統(tǒng)放進(jìn)一個(gè)萬能柜子(通常是 Map<String, Object>):
@JsonAnySetter
publicvoidadd(String key, Object value){
otherProps.put(key, value);
}
比如這個(gè) JSON:
{
"name": "豆瓣醬",
"spicy": true,
"limited_edition": "yes",
"extra_notes": "只在冬天賣"
}
你類里只定義了 name 和 spicy 字段,但 limited_edition 和 extra_notes 也能順利進(jìn)貨,被收納進(jìn)了 otherProps 這個(gè)萬能抽屜里。
@JsonAnySetter 用于標(biāo)注一個(gè)方法,該方法可以接收 JSON 中沒有預(yù)定義的屬性。當(dāng) Jackson 反序列化 JSON 時(shí),如果遇到未在 Java 類中顯式定義的字段,它會(huì)調(diào)用這個(gè)方法并將字段名和字段值作為參數(shù)傳遞給它。
當(dāng)你在反序列化 JSON 時(shí),不希望顯式定義所有的字段,或者 JSON 中包含了動(dòng)態(tài)的屬性時(shí),使用 @JsonAnySetter 可以自動(dòng)將這些字段添加到一個(gè) Map 或類似的結(jié)構(gòu)中。
接下來用一個(gè)完整的代碼示例,我們來實(shí)現(xiàn)反序列化時(shí)動(dòng)態(tài)添加屬性:
public class Person{
private String name;
privateint age;
// 存儲(chǔ)額外的動(dòng)態(tài)屬性
private Map<String, Object> additionalProperties = new HashMap<>();
// 添加動(dòng)態(tài)屬性
@JsonAnySetter
publicvoidaddAdditionalProperty(String key, Object value){
this.additionalProperties.put(key, value);
}
// 省略 getter 和 setter 方法
public Map<String, Object> getAdditionalProperties(){
return additionalProperties;
}
publicstaticvoidmain(String[] args)throws Exception {
String json = "{"name":"John","age":30,"address":"123 Street","nickname":"Johnny"}";
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(json, Person.class);
System.out.println("Name: " + person.name); // 輸出:Name: John
System.out.println("Age: " + person.age); // 輸出:Age: 30
System.out.println("Additional Properties: " + person.getAdditionalProperties());
// 輸出:Additional Properties: {address=123 Street, nickname=Johnny}
}
}
在這個(gè)例子中:
- Person 類通過 @JsonAnySetter 注解的 addAdditionalProperty 方法來處理動(dòng)態(tài)的屬性。
- Person 類中沒有顯式的字段來接收 address 和 nickname,但它們被添加到 additionalProperties 中。
- 當(dāng) Jackson 反序列化 JSON 時(shí),它會(huì)把 address 和 nickname 作為動(dòng)態(tài)屬性添加到 additionalProperties 中。
輸出:
Name: John
Age: 30
Additional Properties: {address=123 Street, nickname=Johnny}
@JsonAnyGetter:隨便拿!需要啥我都能給
到了序列化的時(shí)候,有顧客問你:“老板,這罐醬料里都包含什么成分?”
你就把主料(已有字段)和萬能抽屜里的額外信息一起打包給他,看起來就像全是標(biāo)準(zhǔn)字段一樣輸出!
@JsonAnyGetter
public Map<String, Object> getOtherProps(){
return otherProps;
}
這樣序列化輸出的 JSON 會(huì)自動(dòng)把 otherProps 里的內(nèi)容平鋪出去,和其他字段“融為一體”。
@JsonAnyGetter 用于標(biāo)注一個(gè)方法,該方法返回一個(gè) Map 或類似結(jié)構(gòu),它將包含對(duì)象的 動(dòng)態(tài)屬性(即對(duì)象中沒有顯式定義的字段)。當(dāng) Jackson 序列化對(duì)象時(shí),它會(huì)將這個(gè) Map 中的鍵值對(duì)當(dāng)作額外的 JSON 屬性來序列化。
當(dāng)你有一個(gè)類,但是它可能會(huì)接受動(dòng)態(tài)的字段,或者一些額外的鍵值對(duì)時(shí),使用 @JsonAnyGetter 允許你將這些額外的字段序列化為 JSON。
繼續(xù)使用上面的person類,它有一些基本的屬性,但你希望允許動(dòng)態(tài)添加額外的屬性,如額外的 "address" 、 "nickname" 等字段。
public class Person {
private String name;
privateint age;
// 存儲(chǔ)額外的動(dòng)態(tài)屬性
private Map<String, Object> additionalProperties = new HashMap<>();
publicPerson(String name, int age){
this.name = name;
this.age = age;
}
// 通過該方法返回所有額外的動(dòng)態(tài)屬性
@JsonAnyGetter
public Map<String, Object> getAdditionalProperties(){
return additionalProperties;
}
publicvoidaddAdditionalProperty(String key, Object value){
this.additionalProperties.put(key, value);
}
// 省略 getter 和 setter 方法
}
public class Main {
publicstaticvoidmain(String[] args)throws Exception {
Person person = new Person("John", 30);
person.addAdditionalProperty("address", "123 Street");
person.addAdditionalProperty("nickname", "Johnny");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(person);
System.out.println(json); // 輸出:{"name":"John","age":30,"address":"123 Street","nickname":"Johnny"}
}
}
在這個(gè)例子中:
- Person 類包含一個(gè) Map<String, Object> 來存儲(chǔ)動(dòng)態(tài)屬性。
- 使用 @JsonAnyGetter 標(biāo)注 getAdditionalProperties() 方法,表示 additionalProperties 中的鍵值對(duì)應(yīng)該被序列化為 JSON 字段。
- 通過調(diào)用 addAdditionalProperty() 方法向 additionalProperties 中添加動(dòng)態(tài)字段。
輸出:
{
"name": "John",
"age": 30,
"address": "123 Street",
"nickname": "Johnny"
}
總結(jié)一下
圖片
特別適合那些字段不固定、可能需要?jiǎng)討B(tài)擴(kuò)展的 JSON 數(shù)據(jù)結(jié)構(gòu),比如配置項(xiàng)、參數(shù)列表、插件信息等。