7

FastJSON字段智能匹配踩坑

 3 years ago
source link: https://wakzz.cn/2021/01/01/java/FastJSON%E5%AD%97%E6%AE%B5%E6%99%BA%E8%83%BD%E5%8C%B9%E9%85%8D%E8%B8%A9%E5%9D%91/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

FastJSON字段智能匹配踩坑

祈雨的博客
2021-01-01

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);
}

image

高版本的智能匹配规则的关键代码如下,翻译成人话就是:

  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);
}

image

高版本与低版本的智能匹配规则差异就是:如果JavaBean字段有@JSONField注解且name不空时,低配版对name的值会忽略字母大小写和-,_两个字符,而高版本则直接取name的值不会做忽略操作。

因此示例中加了@JSONField注解的labelId字段才会因为FastJSON版本不同而导致反序列化结果的不同。

在对FastJSON的最新几个版本挨个排查后定位出智能匹配规则发生修改的版本为1.2.71,所以如果代码中使用了智能匹配,那么建议谨慎升级到1.2.71及其更高的版本。

另外这么明显的未向前兼容的规则修改,应该有很多人会踩坑。于是去FastJSON的GitHub上查看后果然已经有人提出了issues:1.2.71以上版本加了JSONField的字段无法反序列化


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK