• 疫情宅在家,研究一下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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    输入的字符串为:

    {
        "age": 28,
        "user_id": 123,
        "user_name": "海洋哥"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    输出结果为:

    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多行***
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    再看下核心的代码,智能匹配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;
        }
    
    • 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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    通过上面的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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    从源码可以看出,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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    从源码可以看出,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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    总结

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

  • 相关阅读:
    redis高可用——主从复制、哨兵模式、cluster集群
    vue学习笔记22-组件传递多种数据类型&props效验
    关于loss.backward()optimizer.step()optimizer.zero_grad()的顺序
    vue,uniapp生成二维码
    Cmake入门(一文读懂)
    Hadoop集群搭建之Hadoop组件安装
    【Linux】UDP的服务端 + 客户端
    唯有自身强大才能呼风唤雨—Intel要携CXL一统互联江湖了吗?
    数据结构第一章:部分答案
    1527. 患某种疾病的患者
  • 原文地址:https://blog.csdn.net/puhaiyang/article/details/126773398