记一次 FastJson 的踩坑经历

  Java大联盟

  帮助万千Java学习者持续成长

关注

项目和第三方对接,有接口一直调不通,直到我拿到合作方的入参后,问题来了,有了接下来的一系列故事。

拿到原生的第三方参数( JSON 格式的),为了节省时间,迫不及待地直接在本地单元测试一波。熟悉的操作,这里为了方便观察,我模拟一次请求从 Controller 进入的请求:

import com.alibaba.fastjson.JSON;import lombok.Data;


public class ProFastjson {
    @Data
    static class Teacher {
        private String teacherName;
    }


    private static TeacherController teacherController = new TeacherController();


    public static void main(String[] args) {
        String str = "{\"teacher_name\":\"zxerjones\"}";
        Teacher teacher = JSON.parseObject(str, Teacher.class);
        teacherController.search(teacher);
    }
}

看重点:我的 JSONString 参数名有下划线,Teacher 的是没有 teacher_name 这个属性的,反序列化之后的值应该是空的,可偏偏方法走通了。不甘心的我决定 debug 一波,如下图:

记一次 FastJson 的踩坑经历_第1张图片

反序列化结果

反序列化成功了,是不是一脸懵逼。没错 FASTJson 就是不和你讲道理。行吧,那一定是是 FASTJson 内部的反序列化机制造成这样的结果,debug 进源码看看:

记一次 FastJson 的踩坑经历_第2张图片

JSON.parseObject 方法内部

注意看上图 378 行,JSON 的反序列化就是在这个方法中实现的,继续跟进:

记一次 FastJson 的踩坑经历_第3张图片

DefaultJSONParser.parseObject 方法内部

上图对一些规则进行校验之后,开始进入主题。正式调用反序列化工具 JavaBeanDeserializer.deserialze 对字符进行反序列化。这个方法主要做的事情就是对 JSON 字符串内部的键值对和我们需要的反序列化结果类进行绑定,赋值(这个方法代码比较多,只贴重点):下图是赋值的过程,这才是导致问题的关键:

记一次 FastJson 的踩坑经历_第4张图片

JavaBeanDeserializer.deserialze 方法内部

就是 850 行代码,终于找到问题的所在了,马上就真相大白。现在可以确定就是 parseField 方法搞的鬼,继续点进去:

记一次 FastJson 的踩坑经历_第5张图片

parseField 方法内部

上图方法内部做的事情就是生成 field 反序列化工具对 field 反序列化,然后赋值。这个 field 反序列化工具从何而来断点打到 1089 行,可以看到,这个方法返回了我们需要的工具,点进去:

记一次 FastJson 的踩坑经历_第6张图片

JavaBeanDeserializer.smartMatch 方法内部

代码做的事情就是根据 JSON 字符串的键(teacher_name)做一次 hash 运算,然后与需要反序列化结果(Teacher.class)中的所有参数(这里只有teacherName)做 hash 运算,根据生成的 hash 值来对 field 进行配对。OK就是这样的,现在我们已经找到问题的根源,hash 之后的值是相同的。fastjosn 内部封装了计算 hash 的工具类,TypeUtils.fnv1a_64_lower,如下图:

记一次 FastJson 的踩坑经历_第7张图片

TypeUtils.fnv1a_64_lower

代码中可以看到进行hash的两个规则:

1、‘-’和‘_’不参与hash运算。

2、忽略大小写。

根据最后得出的 hash 规则,那么可以在 json 字符串中随意的添加"_","-",并且大小写忽略都可以反序列化成功,证实下猜想:如下代码。

 public static void main(String[] args) {        String str = "{\"TEACHER___---NAME\":\"zxerjones\"}";
        Teacher teacher = JSON.parseObject(str, Teacher.class);
        System.out.println(teacher);
    }

运行结果:

ProFastjson.Teacher(teacherName=zxerjones)


Process finished with exit code 0

猜想成立,问题解决。血一样的教训:能用 postman 就不要用单元测试。~~(╯﹏╰)~~

推荐阅读

1、一次性把JVM讲清楚,别再被面试官问住了

2、Spring Boot源码解析

3、一文搞懂前后端分离

4、快速上手Spring Boot+Vue前后端分离

楠哥简介

资深 Java 工程师,微信号 nnsouthwind

《Java零基础实战》一书作者,今日头条认证大V

GitChat认证作者,B站认证UP主(楠哥教你学Java)

致力于帮助万千 Java 学习者持续成长。

 

你可能感兴趣的:(记一次 FastJson 的踩坑经历)