FastJSON字段智能匹配踩坑的解決
背景
2021年第一天早上,客戶突然投訴說系統(tǒng)的一個功能出了問題,緊急排查后發(fā)現(xiàn)后端系統(tǒng)確實出了bug,原因為前端傳輸?shù)腏SON報文,后端反序列化成JavaBean后部分字段的值丟失了。
查看git提交歷史記錄,前端和后端近期并未對該功能的接口字段做任何修改,聯(lián)想到上個版本升級了后端的FastJSON的版本,懷疑是后端系統(tǒng)對FastJSON升級導(dǎo)致的問題。
復(fù)現(xiàn)
@Data
static class Label {
@JSONField(name = "label_id")
private Integer labelId;
private String labelName;
}
public static void main(String[] args) {
String value = "{'labelId': 1,'label_name':'name'}";
Label label = JSON.parseObject(value, Label.class);
System.out.println(label);
}
低版本
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.60</version> </dependency>
使用低版本FastJSON,如上使用1.2.60版本,示例輸出的結(jié)果如下,即兩個字段JSON解析映射成功。雖然JavaBean中的字段和JSON中的key并不完全匹配(大小寫不匹配以及下劃線匹配),但得益于FastJSON的智能匹配,忽略了大小寫和下劃線,依然將JSON映射成功。
Label(labelId=1, labelName=name)
高版本
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.71</version> </dependency>
使用高版本FastJSON,如上使用1.2.71,示例輸出結(jié)果如下,字段labelId映射失敗,即高版本FastJSON對智能匹配規(guī)則做了修改,并且未向前兼容而導(dǎo)致了部分字段映射失敗導(dǎo)致了這次的bug。
Label(labelId=null, labelName=name)
原理
解析高版本FastJSON字段智能匹配失敗的原因,首先要先了解智能匹配的規(guī)則。
低版本
低版本的智能匹配規(guī)則的關(guān)鍵代碼如下,翻譯成人話就是:
1、如果JavaBean字段有@JSONField注解且name不空時,則對name的值忽略字母大小寫和-,_兩個字符
2、否則取JavaBean的字段名,忽略字母大小寫和-,_兩個字符
3、JSON中的key忽略is開頭并忽略剩余字母大小寫和-,_兩個字符
// 對JSON中沒有成功映射JavaBean的key做智能匹配
// 1. 忽略key的字母大小寫和'-','_'兩個字符
long smartKeyHash = TypeUtils.fnv1a_64_lower(key);
if (this.smartMatchHashArray == null) {
long[] hashArray = new long[sortedFieldDeserializers.length];
for (int i = 0; i < sortedFieldDeserializers.length; i++) {
// fieldInfo.name優(yōu)先取@JSONField的name字段,其次取JavaBean字段名
// fieldInfo.name忽略字母大小寫和'-','_'兩個字符嘗試與JSON中的key做智能匹配
hashArray[i] = TypeUtils.fnv1a_64_lower(sortedFieldDeserializers[i].fieldInfo.name);
}
Arrays.sort(hashArray);
this.smartMatchHashArray = hashArray;
}
int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
// 2. 如果key以'is'開頭,則忽略'is'開頭并忽略剩余字母大小寫和'-','_'兩個字符
boolean is = false;
if (pos < 0 && (is = key.startsWith("is"))) {
smartKeyHash = TypeUtils.fnv1a_64_lower(key.substring(2));
pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
}

高版本
高版本的智能匹配規(guī)則的關(guān)鍵代碼如下,翻譯成人話就是:
1、如果JavaBean字段有@JSONField注解且name不空時,則取name的值
2、否則取JavaBean的字段名,忽略字母大小寫和-,_兩個字符
3、JSON中的key忽略is開頭并忽略剩余字母大小寫和-,_兩個字符
if (this.smartMatchHashArray == null) {
long[] hashArray = new long[sortedFieldDeserializers.length];
for (int i = 0; i < sortedFieldDeserializers.length; i++) {
// 1. @JSONField的name不空時取該值直接與JSON中的key做匹配
// 2. 取JavaBean字段名忽略字母大小寫和'-','_'兩個字符嘗試與JSON中的key做智能匹配
hashArray[i] = sortedFieldDeserializers[i].fieldInfo.nameHashCode;
}
Arrays.sort(hashArray);
this.smartMatchHashArray = hashArray;
}
// 對JSON中沒有成功映射JavaBean的key做智能匹配
// 1. 直接匹配
long smartKeyHash = TypeUtils.fnv1a_64_extract(key);
int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
// 2. 忽略key的字母大小寫和'-','_'兩個字符
if (pos < 0) {
long smartKeyHash1 = TypeUtils.fnv1a_64_lower(key);
pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash1);
}
// 3. 如果key以'is'開頭,則忽略'is'開頭并忽略剩余字母大小寫和'-','_'兩個字符
boolean is = false;
if (pos < 0 && (is = key.startsWith("is"))) {
smartKeyHash = TypeUtils.fnv1a_64_lower(key.substring(2));
pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
}
// 優(yōu)先取@JSONField的name字段直接與JSON中的key做匹配
// 其次取JavaBean字段名忽略字母大小寫和'-','_'兩個字符嘗試與JSON中的key做智能匹配
private long nameHashCode64(String name, JSONField annotation)
{
if (annotation != null && annotation.name().length() != 0) {
return TypeUtils.fnv1a_64_extract(name);
}
return TypeUtils.fnv1a_64_lower(name);
}

區(qū)別
高版本與低版本的智能匹配規(guī)則差異就是:如果JavaBean字段有@JSONField注解且name不空時,低配版對name的值會忽略字母大小寫和-,_兩個字符,而高版本則直接取name的值不會做忽略操作。
因此示例中加了@JSONField注解的labelId字段才會因為FastJSON版本不同而導(dǎo)致反序列化結(jié)果的不同。
在對FastJSON的最新幾個版本挨個排查后定位出智能匹配規(guī)則發(fā)生修改的版本為1.2.71,所以如果代碼中使用了智能匹配,那么建議謹(jǐn)慎升級到1.2.71及其更高的版本。
另外這么明顯的未向前兼容的規(guī)則修改,應(yīng)該有很多人會踩坑。于是去FastJSON的GitHub上查看后果然已經(jīng)有人提出了issues:1.2.71以上版本加了JSONField的字段無法反序列化。
FastJSON解析數(shù)據(jù),字段數(shù)據(jù)不匹配問題
FastJSON中@JSONField注解使用
有個聯(lián)通的數(shù)據(jù)要解析出來存入數(shù)據(jù)庫,但是提供過來的json數(shù)據(jù)有特殊符號'.','-',之前想著直接把特殊的字符給替換掉,解析出來
有一種是可以在實體類上加注解來替換轉(zhuǎn)出來的
fastjson的key是根據(jù)javabean里面的getter和setter方法來的,不是根據(jù)屬性名的,所以會出現(xiàn)這個問題,你在屬性的get和set方法上面寫上標(biāo)注,說明轉(zhuǎn)成什么就行了比如 @JSONField(name=”SOMETHING”)
之前想的是替換到j(luò)son數(shù)據(jù)里面的特殊字符,然后把實體類的.-都替換掉,這樣就可以創(chuàng)建實體類對象了,然后在用fastjson轉(zhuǎn)成對象
后來知道有fastjson的注解的@JSONField(name="name.age-12"來映射上實體類的)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
簡單了解JAVA內(nèi)存泄漏和溢出區(qū)別及聯(lián)系
這篇文章主要介紹了簡單了解JAVA內(nèi)存泄漏和溢出區(qū)別及聯(lián)系,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03
Spring兩種任務(wù)調(diào)度Scheduled和Async的區(qū)別和應(yīng)用場景詳解
在現(xiàn)代應(yīng)用程序中,任務(wù)調(diào)度是一個非常普遍的需求,Spring框架提供了兩種主要的方式來實現(xiàn)任務(wù)調(diào)度:??Scheduled?? 和 ??Async??,在這篇文章中,我們將詳細(xì)介紹這兩種方式的區(qū)別和應(yīng)用場景,需要的朋友可以參考下2024-12-12
SpringBoot如何讀取application.properties配置文件
這篇文章主要介紹了SpringBoot如何讀取application.properties配置文件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
IDEA取消SVN關(guān)聯(lián),再重新分享項目的操作
這篇文章主要介紹了IDEA取消SVN關(guān)聯(lián),再重新分享項目的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
解決java idea新建子目錄時命名不是樹形結(jié)構(gòu)的問題
這篇文章主要介紹了解決java idea新建子目錄時命名不是樹形結(jié)構(gòu)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
MyBatis實現(xiàn)兩種查詢樹形數(shù)據(jù)的方法詳解(嵌套結(jié)果集和遞歸查詢)
樹形結(jié)構(gòu)數(shù)據(jù)在開發(fā)中十分常見,比如:菜單數(shù)、組織樹, 利用 MyBatis 提供嵌套查詢功能可以很方便地實現(xiàn)這個功能需求。本文主要介紹了兩種方法,感興趣的可以了解一下2021-09-09
SpringBoot實現(xiàn)權(quán)限驗證的示例步驟
權(quán)限驗證是一種用于控制對系統(tǒng)資源和操作的訪問的機(jī)制。它允許開發(fā)人員定義誰可以執(zhí)行特定操作或訪問特定資源,并確保只有經(jīng)過授權(quán)的用戶才能執(zhí)行這些操作,這篇文章主要介紹了SpringBoot實現(xiàn)權(quán)限驗證,需要的朋友可以參考下2023-08-08

