惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏
罗磊的独立博客
博客园 - 【当耐特】
M
MIT News - Artificial intelligence
D
Docker
博客园 - 三生石上(FineUI控件)
博客园 - 司徒正美
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
月光博客
月光博客
S
SegmentFault 最新的问题
Jina AI
Jina AI
Blog — PlanetScale
Blog — PlanetScale
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - Franky
L
LangChain Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Azure Blog
Microsoft Azure Blog
阮一峰的网络日志
阮一峰的网络日志
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI

祈雨的笔记

安全多方计算MPC spark原理解析 kueue执行源码分析 spark on k8s执行源码分析 spark-operator源码解析 系统压测遇到的缓存击穿问题 我的世界PC与安卓联机 蚂蚁金服流量投放平台的AIG改造 G1大对象致Old区占用率高 日志打印导致接口响应率下跌分析 Groovy加载类导致OOM分析 ERROR日志打印导致CPU满载 记OceanBase死锁超时 应用发版期间服务响应超时 Ark Serverless初探 系统优化复盘一二三 The user specified as a definer does not exist Kong网关初探 API网关选型调研 CPU火焰图常用工具 配置中心选型调研 root操作Nginx导致用户组错误 基于Proxifier使用代理 Nacos初探 记一次Nginx服务器CPU满荷载故障 基于券系统分库分表的思考 limit不参与SQL成本计算致索引失效 Linux常用性能监控命令 golang低版本http2偶现400 hostname in certificate didn't match
FastJSON字段智能匹配踩坑
祈雨的笔记 · 2020-12-31 · via 祈雨的笔记

背景

2021年第一天早上,客户突然投诉说系统的一个功能出了问题,紧急排查后发现后端系统确实出了bug,原因为前端传输的JSON报文,后端反序列化成JavaBean后部分字段的值丢失了。

查看git提交历史记录,前端和后端近期并未对该功能的接口字段做任何修改,联想到上个版本升级了后端的FastJSON的版本,怀疑是后端系统对FastJSON升级导致的问题。

复现

1
2
3
4
5
6
7
8
9
10
11
12
@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);
}

低版本

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>

使用低版本FastJSON,如上使用1.2.60版本,示例输出的结果如下,即两个字段JSON解析映射成功。虽然JavaBean中的字段和JSON中的key并不完全匹配(大小写不匹配以及下划线匹配),但得益于FastJSON的智能匹配,忽略了大小写和下划线,依然将JSON映射成功。

1
Label(labelId=1, labelName=name)

高版本

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.71</version>
</dependency>

使用高版本FastJSON,如上使用1.2.71,示例输出结果如下,字段labelId映射失败,即高版本FastJSON对智能匹配规则做了修改,并且未向前兼容而导致了部分字段映射失败导致了这次的bug。

1
Label(labelId=null, labelName=name)

原理

解析高版本FastJSON字段智能匹配失败的原因,首先要先了解智能匹配的规则。

低版本

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

  1. 如果JavaBean字段有@JSONField注解且name不空时,则对name的值忽略字母大小写和-,_两个字符
  2. 否则取JavaBean的字段名,忽略字母大小写和-,_两个字符
  3. JSON中的key忽略is开头并忽略剩余字母大小写和-,_两个字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21


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++) {


hashArray[i] = TypeUtils.fnv1a_64_lower(sortedFieldDeserializers[i].fieldInfo.name);
}
Arrays.sort(hashArray);
this.smartMatchHashArray = hashArray;
}

int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);

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开头并忽略剩余字母大小写和-,_两个字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
if (this.smartMatchHashArray == null) {
long[] hashArray = new long[sortedFieldDeserializers.length];
for (int i = 0; i < sortedFieldDeserializers.length; i++) {


hashArray[i] = sortedFieldDeserializers[i].fieldInfo.nameHashCode;
}
Arrays.sort(hashArray);
this.smartMatchHashArray = hashArray;
}



long smartKeyHash = TypeUtils.fnv1a_64_extract(key);
int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);

if (pos < 0) {
long smartKeyHash1 = TypeUtils.fnv1a_64_lower(key);
pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash1);
}

boolean is = false;
if (pos < 0 && (is = key.startsWith("is"))) {
smartKeyHash = TypeUtils.fnv1a_64_lower(key.substring(2));
pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
}
1
2
3
4
5
6
7
8
9


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的字段无法反序列化