当前位置: 首页 > news >正文

疫情宅在家,研究一下fastjson中字段智能匹配的原理

示例

有次看到项目中同事用驼峰(Camel-Case)命名法定义的字段来直接接收对方下划线命名的字段,刚开始我还以为有BUG

有妖气

结果一测试:

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

public class FastJsonTestMain {
    public static void main(String[] args) {
        String jsonStr = "{\n" +
                "    \"age\": 28,\n" +
                "    \"user_id\": 123,\n" +
                "    \"user_name\": \"海洋哥\"\n" +
                "}";
        UserInfo userInfo = JSON.parseObject(jsonStr, UserInfo.class);
        System.out.println(userInfo.toString());
    }

    @Data
    public static class UserInfo {
        private Integer age;
        private Long userId;
        private String userName;
    }
}

输入的字符串为:

{
    "age": 28,
    "user_id": 123,
    "user_name": "海洋哥"
}

输出结果为:

FastJsonTestMain.UserInfo(age=28, userId=123, userName=海洋哥)

才明白原来菜的人是我自己。

fastjson反序列化时,是能自动下划线转驼峰的。

但换了其他的json框架发现又不行,趁着疫情关在家,决定还是来研究一下fastjson中它自动转驼峰的原理。

fastjson智能匹配处理过程

fastjson在进行反序列化的时候,对每一个json字段的key值解析时,会调用
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField
这个方法

debug

以上面的例子为例,通过debug打个断点看一下解析user_id时的处理逻辑。

此时这个方法中的key为user_id,object为要反序列化的结果对象,这个例子中就是FastJsonTestMain.UserInfo

    public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType,
                              Map<String, Object> fieldValues, int[] setFlags) {
        JSONLexer lexer = parser.lexer; // xxx
        //是否禁用智能匹配;
        final int disableFieldSmartMatchMask = Feature.DisableFieldSmartMatch.mask;
        final int initStringFieldAsEmpty = Feature.InitStringFieldAsEmpty.mask;
        FieldDeserializer fieldDeserializer;
        if (lexer.isEnabled(disableFieldSmartMatchMask) || (this.beanInfo.parserFeatures & disableFieldSmartMatchMask) != 0) {
            fieldDeserializer = getFieldDeserializer(key);
        } else if (lexer.isEnabled(initStringFieldAsEmpty) || (this.beanInfo.parserFeatures & initStringFieldAsEmpty) != 0) {
            fieldDeserializer = smartMatch(key);
        } else {
            //进行智能匹配
            fieldDeserializer = smartMatch(key, setFlags);
        }
    
    ***此处省略N多行***
    }

再看下核心的代码,智能匹配smartMatch

public FieldDeserializer smartMatch(String key, int[] setFlags) {
        if (key == null) {
            return null;
        }
        
        FieldDeserializer fieldDeserializer = getFieldDeserializer(key, setFlags);

        if (fieldDeserializer == null) {
            if (this.smartMatchHashArray == null) {
                long[] hashArray = new long[sortedFieldDeserializers.length];
                for (int i = 0; i < sortedFieldDeserializers.length; i++) {
                	//java字段的nameHashCode,源码见下方
                    hashArray[i] = sortedFieldDeserializers[i].fieldInfo.nameHashCode;
                }
                //获取出反序列化目标对象的字段名称hashcode值,并进行排序
                Arrays.sort(hashArray);
                this.smartMatchHashArray = hashArray;
            }

            // smartMatchHashArrayMapping
            long smartKeyHash = TypeUtils.fnv1a_64_lower(key);
            //进行二分查找,判断是否找到
            int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
            if (pos < 0) {
                //原始字段没有匹配到,用fnv1a_64_extract处理一下再次匹配
                long smartKeyHash1 = TypeUtils.fnv1a_64_extract(key);
                pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash1);
            }

            boolean is = false;
            if (pos < 0 && (is = key.startsWith("is"))) {
                //上面的操作后仍然没有匹配到,把is去掉后再次进行匹配
                smartKeyHash = TypeUtils.fnv1a_64_extract(key.substring(2));
                pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
            }

            if (pos >= 0) {
                //通过智能匹配字段匹配成功
                if (smartMatchHashArrayMapping == null) {
                    short[] mapping = new short[smartMatchHashArray.length];
                    Arrays.fill(mapping, (short) -1);
                    for (int i = 0; i < sortedFieldDeserializers.length; i++) {
                        int p = Arrays.binarySearch(smartMatchHashArray, sortedFieldDeserializers[i].fieldInfo.nameHashCode);
                        if (p >= 0) {
                            mapping[p] = (short) i;
                        }
                    }
                    smartMatchHashArrayMapping = mapping;
                }

                int deserIndex = smartMatchHashArrayMapping[pos];
                if (deserIndex != -1) {
                    if (!isSetFlag(deserIndex, setFlags)) {
                        fieldDeserializer = sortedFieldDeserializers[deserIndex];
                    }
                }
            }

            if (fieldDeserializer != null) {
                FieldInfo fieldInfo = fieldDeserializer.fieldInfo;
                if ((fieldInfo.parserFeatures & Feature.DisableFieldSmartMatch.mask) != 0) {
                    return null;
                }

                Class fieldClass = fieldInfo.fieldClass;
                if (is && (fieldClass != boolean.class && fieldClass != Boolean.class)) {
                    fieldDeserializer = null;
                }
            }
        }


        return fieldDeserializer;
    }

通过上面的smartMatch方法可以看出,fastjson中之所以能做到下划线自动转驼峰,主要还是因为在进行字段对比时,使用了fnv1a_64_lower和fnv1a_64_extract方法进行了处理。

fnv1a_64_extract方法源码:

    public static long fnv1a_64_extract(String key) {
        long hashCode = fnv1a_64_magic_hashcode;
        for (int i = 0; i < key.length(); ++i) {
            char ch = key.charAt(i);
            //去掉下划线和减号
            if (ch == '_' || ch == '-') {
                continue;
            }
            //大写转小写
            if (ch >= 'A' && ch <= 'Z') {
                ch = (char) (ch + 32);
            }
            hashCode ^= ch;
            hashCode *= fnv1a_64_magic_prime;
        }
        return hashCode;
    }

从源码可以看出,fnv1a_64_extract方法主要做了这个事:
去掉下划线、减号,并大写转小写

fnv1a_64_lower方法源码:

    public static long fnv1a_64_lower(String key) {
        long hashCode = fnv1a_64_magic_hashcode;
        for (int i = 0; i < key.length(); ++i) {
            char ch = key.charAt(i);
            if (ch >= 'A' && ch <= 'Z') {
                ch = (char) (ch + 32);
            }
            hashCode ^= ch;
            hashCode *= fnv1a_64_magic_prime;
        }
        return hashCode;
    }

从源码可以看出,fnv1a_64_lower方法做了大写转小写的功能

其次上面代码中的fieldInfo.nameHashCode的源码也有必要贴一下:
com.alibaba.fastjson.util.FieldInfo#nameHashCode64

    private long nameHashCode64(String name, JSONField annotation)
    {
        //先从@JSONField注解的name中取值
        if (annotation != null && annotation.name().length() != 0) {
            return TypeUtils.fnv1a_64_lower(name);
        }
        //没有@JSONField注解,直接使用java对象的字段
        return TypeUtils.fnv1a_64_extract(name);
    }

总结

fastjson中字段智能匹配的原理是在字段匹配时,使用了TypeUtils.fnv1a_64_lower方法对字段进行全体转小写处理。

之后再用TypeUtils.fnv1a_64_extract方法对json字段进行去掉"_“和”-"符号,再全体转小写处理。

如果上面的操作仍然没有匹配成功,会再进行一次去掉json字段中的is再次进行匹配。

match

再简化一点描述:

  1. A是java类对象中的一个字段,B是json串的一个字段,A与B的字段进行匹配。
  2. A的值优先从@JSONField的name值去取,取到的话就用@JSONField.name的小写值。没取到的话直接使用A的值,去掉"-“、”_"并转小写。
  3. B的值为json串的值,先使用它的全体小写值与A进行匹配,没有匹配成功再用B的值去掉"-“、”“并转小写进行匹配。如果仍然没有匹配成功且B是以is开头的,则用B去掉is、”-“、”"并转小写进行匹配。

其他

fastjson中smartMatch是默认开启的,如果想要关闭,指定DisableFieldSmartMatch即可

JSONObject.parseObject(strJson, class, Feature.DisableFieldSmartMatch);

例如:

UserInfo userInfo = JSON.parseObject(jsonStr, UserInfo.class, Feature.DisableFieldSmartMatch);

相关文章:

  • 【MapGIS精品教程】001:MapGIS K9完整图文安装教程
  • 指针和数组笔试题解析
  • 人脸检测5种方法
  • SparkSQL 总结(未完待续)
  • 51单片机入门——数模\模数转换
  • 【毕业设计】 单片机自动写字机器人设计与实现 - 物联网 嵌入式 stm32
  • 花边新闻获取易语言代码
  • HTML5入门(1)——HTML基础
  • 计算机网络体概念
  • redis底层都有哪些数据结构?带你了解redis是如何存储数据的
  • 通道分离与合并、彩色图转换为灰度图、二值化
  • C语言经典算法实例4:判断回文数
  • 基于php+mysql的菜品食谱美食网
  • upload-labs靶场通关指南(第1-3关)
  • Android Studio 利用系统签名打包apk
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • Gradle 5.0 正式版发布
  • java 多线程基础, 我觉得还是有必要看看的
  • Java基本数据类型之Number
  • SpiderData 2019年2月13日 DApp数据排行榜
  • SpiderData 2019年2月23日 DApp数据排行榜
  • tab.js分享及浏览器兼容性问题汇总
  • tweak 支持第三方库
  • Vue2.x学习三:事件处理生命周期钩子
  • vue-loader 源码解析系列之 selector
  • 翻译:Hystrix - How To Use
  • 面试遇到的一些题
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 树莓派 - 使用须知
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • 主流的CSS水平和垂直居中技术大全
  • Linux权限管理(week1_day5)--技术流ken
  • 阿里云移动端播放器高级功能介绍
  • 进程与线程(三)——进程/线程间通信
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • $NOIp2018$劝退记
  • (pojstep1.1.1)poj 1298(直叙式模拟)
  • (八)c52学习之旅-中断实验
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (论文阅读40-45)图像描述1
  • (免费领源码)Java#Springboot#mysql农产品销售管理系统47627-计算机毕业设计项目选题推荐
  • (一) springboot详细介绍
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (转)德国人的记事本
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .CSS-hover 的解释
  • .jks文件(JAVA KeyStore)
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .NET HttpWebRequest、WebClient、HttpClient
  • .Net MVC4 上传大文件,并保存表单
  • .NET/C# 使用反射调用含 ref 或 out 参数的方法
  • .Net6支持的操作系统版本(.net8已来,你还在用.netframework4.5吗)
  • .NET处理HTTP请求
  • .php文件都打不开,打不开php文件怎么办