FastJSON字段智能匹配踩坑的解決
背景
2021年第一天早上,客戶突然投訴說系統的一個功能出瞭問題,緊急排查後發現後端系統確實出瞭bug,原因為前端傳輸的JSON報文,後端反序列化成JavaBean後部分字段的值丟失瞭。
查看git提交歷史記錄,前端和後端近期並未對該功能的接口字段做任何修改,聯想到上個版本升級瞭後端的FastJSON的版本,懷疑是後端系統對FastJSON升級導致的問題。
復現
@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版本,示例輸出的結果如下,即兩個字段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,示例輸出結果如下,字段labelId映射失敗,即高版本FastJSON對智能匹配規則做瞭修改,並且未向前兼容而導致瞭部分字段映射失敗導致瞭這次的bug。
Label(labelId=null, labelName=name)
原理
解析高版本FastJSON字段智能匹配失敗的原因,首先要先瞭解智能匹配的規則。
低版本
低版本的智能匹配規則的關鍵代碼如下,翻譯成人話就是:
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優先取@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); }
高版本
高版本的智能匹配規則的關鍵代碼如下,翻譯成人話就是:
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); }
// 優先取@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); }
區別
高版本與低版本的智能匹配規則差異就是:如果JavaBean字段有@JSONField註解且name不空時,低配版對name的值會忽略字母大小寫和-,_兩個字符,而高版本則直接取name的值不會做忽略操作。
因此示例中加瞭@JSONField註解的labelId字段才會因為FastJSON版本不同而導致反序列化結果的不同。
在對FastJSON的最新幾個版本挨個排查後定位出智能匹配規則發生修改的版本為1.2.71,所以如果代碼中使用瞭智能匹配,那麼建議謹慎升級到1.2.71及其更高的版本。
另外這麼明顯的未向前兼容的規則修改,應該有很多人會踩坑。於是去FastJSON的GitHub上查看後果然已經有人提出瞭issues:1.2.71以上版本加瞭JSONField的字段無法反序列化。
FastJSON解析數據,字段數據不匹配問題
FastJSON中@JSONField註解使用
有個聯通的數據要解析出來存入數據庫,但是提供過來的json數據有特殊符號’.’,’-‘,之前想著直接把特殊的字符給替換掉,解析出來
有一種是可以在實體類上加註解來替換轉出來的
fastjson的key是根據javabean裡面的getter和setter方法來的,不是根據屬性名的,所以會出現這個問題,你在屬性的get和set方法上面寫上標註,說明轉成什麼就行瞭比如 @JSONField(name=”SOMETHING”)
之前想的是替換到json數據裡面的特殊字符,然後把實體類的.-都替換掉,這樣就可以創建實體類對象瞭,然後在用fastjson轉成對象
後來知道有fastjson的註解的@JSONField(name=”name.age-12″來映射上實體類的)
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- java fastjson傳輸long數據卻接收到瞭int的問題
- java中map與實體類的相互轉換操作
- 淺談Java中FastJson的使用
- 關於FastJson long 溢出問題的小結
- fastjson全局日期序列化設置導致JSONField失效問題解決方案