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"来映射上实体类的)

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 解决fastjson从1.1.41升级到1.2.28后报错问题详解

    最近因为fastjson安全漏洞,升级jar包时,踩了一些坑. 新版本FastJsonHttpMessageConverter初始化,默认设置MediaType为*/* 背景: 使用Spring RestTemplate,配置如下: <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <constructor-arg ref="ky.cl

  • 解决json串和实体类字段不一致的问题

    这里我们对json串和实体类字段不一致的情况进行一个测试: 首先,我们建立一个实体类:这里简单定义了name,sex,age三个属性,以及get set tostring方法. public class Student { private String name; private String sex; private Integer age; public String getName() { return name; } public String getSex() { return sex

  • 解决FastJson中"$ref重复引用"的问题方法

    解决FastJson中"$ref重复引用"的问题,先来看一个例子吧: public static void main(String[] args) { UserGroup userGroup = new UserGroup().setName("UserGroup"); User user = new User("User"); for (int i = 0; i < 3; i++) { userGroup.addUser(user); }

  • 关于fastjson的@JSONField注解的一些问题(详解)

    @JSONField 看源码它可以作用于字段和方法上. 引用网上说的, 一.作用Field @JSONField作用在Field时,其name不仅定义了输入key的名称,同时也定义了输出的名称. 但是我在使用中,发现并不如上所说. 例如 @JSONField(name="project_id") private Long ProjectID 发现bean 转json的时候并是"project_id":xxx的形式,json转bean的时候也不会把"proj

  • 解决fastjson泛型转换报错的解决方法

    错误信息 Exception in thread "main" java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.xh.demo.UserDO 泛性类 @Data public class ResultSetDTO<T> { private Integer totalSize; private Integer count; private List<T

  • FastJSON字段智能匹配踩坑的解决

    背景 2021年第一天早上,客户突然投诉说系统的一个功能出了问题,紧急排查后发现后端系统确实出了bug,原因为前端传输的JSON报文,后端反序列化成JavaBean后部分字段的值丢失了. 查看git提交历史记录,前端和后端近期并未对该功能的接口字段做任何修改,联想到上个版本升级了后端的FastJSON的版本,怀疑是后端系统对FastJSON升级导致的问题. 复现 @Data static class Label { @JSONField(name = "label_id") priva

  • 基于IOS端微信分享失效的踩坑及解决方法

    最近的一个公众号是基于vue的spa应用,在接入微信分享和微信语音的时候出现了:在Android上一切正常,但是在ios端调用wx.config的时候总是失败,去翻了官方文档也并没有找到解决方案,最后在测试中发现是因为初始化的时候传入的URL的问题.具体过程如下: 微信config接口配置,官方文档如下: 所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支

  • java8 stream的多字段排序实现(踩坑)

    关于java8 的stream排序用法这里不做多说,这里介绍下曾经在多字段排序时遇到过的一个坑. 需求:需要根据id去分组,然后取出每组中行号最大的一个对象值. 想到可以利用stream的多字段排序,先按id去排,再看行号去排,demo代码如下: class Tt{ private int id; private int line; public Tt(int id, int line) { this.id = id; this.line = line; } public int getId()

  • spring-cloud-gateway启动踩坑及解决

    目录 spring-cloud-gateway启动踩坑 1.webflux与mvc不兼容 2.webflux使用netty作为容器 3.后来实验了下 很坑得spring cloud gateway 异常 spring-cloud-gateway启动踩坑 本人使用的版本是2.1.2,以下只记录几个小问题,但确实实实在在的把个人恶心的要死要活的找不到办法,几经挣扎,最终解决. 更可恨的是开发的过程中,没有出现异常,后来由于项目组其它人加了依赖,不知不觉对项目的兼容造成了英雄,真的是被撞的头破血流,才

  • Spring数据库连接池url参数踩坑及解决

    目录 Spring数据库连接池url参数踩坑 遇到的问题 报错情况 解决 修改数据库连接池的url后,还是连接原先的url 问题 例如 Spring数据库连接池url参数踩坑 遇到的问题 报错情况 解决 & ' 字符在xml需要转义为 ' & ' 修改数据库连接池的url后,还是连接原先的url 问题 当修改连接池url之后,访问的还是原来的数据库. 例如 原来: url=jdbc:mysql://192.168.250.227:3306/myshop?characterEncoding=

  • nuxt使用vuex存储及获取用户信息踩坑的解决

    目录 一.背景 二.具体使用方法以及遇到的问题 三.解决办法 一.背景 按公司要求做一个电商网站,考虑到seo,所以用的是nuxt进行开发. 登录之后记录用户信息(user)使用cookie+vuex模式. 二.具体使用方法以及遇到的问题 1.无法解码导致无法转化为对象且不支持中文: 使用nuxt的nuxtServerInit在在服务端的cookie里获取用户信息,但是user在存储时使用js-cookie,客户端已对数据进行编码且可能包含中文,在使用JSON.parse进行string转化为对

  • springboot整合freemarker的踩坑及解决

    目录 springboot整合freemarker踩坑 报错 问题原因 解决方法 springboot freemarker基础配置及使用 1.基础配置 2.基础使用 springboot整合freemarker踩坑 报错 2021-04-23 02:01:18.148 ERROR 9484 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatc

  • vuepress打包部署踩坑及解决

    目录 vuepress打包部署踩坑记录 vuepress打包报错:error Error rendering /: 问题 方案1 方案2 vuepress打包部署踩坑记录 官网教程 官网给了多种部署方案,这里我才用的是部署到github上 在你的项目中,创建一个如下的 deploy.sh 文件(请自行判断去掉高亮行的注释): #!/usr/bin/env sh # 确保脚本抛出遇到的错误 set -e # 生成静态文件 npm run docs:build # 进入生成的文件夹 cd docs/

  • 记VUE3+TS获取组件类型的方法踩坑及解决

    目录 VUE3+TS获取组件类型的方法踩坑 遇到的坑 问题原因 解决办法 VUE3+TS获取组件ref实例 如何获取组件的类型呢? 总结 VUE3+TS获取组件类型的方法踩坑 获取组件类型的方法 const AccountRef = ref<InstanceType<typeof LoginAccount>>() 遇到的坑 typeof LoginAccount一直报红线提示错误 LoginAction: () => vo...' provides no match for

  • Vue3+Tsx给路由加切换动画时的踩坑及解决

    目录 项目场景 样式文件 步骤 最终代码 总结 项目场景 用最新的技术栈Vue+Tsx给后台管理系统路由加动画时,语法上与模板语法有些许不同,记录下自己的踩坑记录 样式文件 新建文件transition.scss,这里用的是若依框架人家写好的样式,写好之后在全局引入该样式文件 // global transition css /* fade */ .fade-enter-active, .fade-leave-active { transition: opacity 0.28s; } .fade

随机推荐