• JSONObject.parseObject将_id赋值给id问题


        最近再写代码的时候发现一个JSONObject.parseObject使用的问题,会将json中_id的值赋值给对象id,最后得到一个错误的id,后来百度没有查到这个问题到底怎么解决,只好看下源码找出解决方式.由于业务代码没办法展开,这里写一个简单的Demo来重现一下问题.

    问题重现

    @Data
    public class Student {
    
        private String id;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    public class Demo {
    
        public static void main(String[] args) {
            String json = "{\"_id\":\"1\",\"id\":\"2\"}";
            Student student = JSON.parseObject(json, Student.class);
            // 1
            System.out.println(student.getId());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们期望student的id的值应该是2,但是在这里用代码输出的确是1

    源码研读

        源码太过复杂不一一展开,只针对为什么会把_id的值赋值给id代码;

    • com.alibaba.fastjson.parser.deserializer.FieldDeserializer#setValue(java.lang.Object, java.lang.Object)

    对属性进行赋值的关键代码;

        @SuppressWarnings({"rawtypes", "unchecked"})
        public void setValue(Object object, Object value) {
            if (value == null //
                    && fieldInfo.fieldClass.isPrimitive()) {
                return;
            } else if (fieldInfo.fieldClass == String.class
                    && fieldInfo.format != null
                    && fieldInfo.format.equals("trim")) {
                value = ((String) value).trim();
            }
    
            try {
                Method method = fieldInfo.method;
                if (method != null) {
                    if (fieldInfo.getOnly) {
                        if (fieldInfo.fieldClass == AtomicInteger.class) {
                            AtomicInteger atomic = (AtomicInteger) method.invoke(object);
                            if (atomic != null) {
                                atomic.set(((AtomicInteger) value).get());
                            } else {
                                degradeValueAssignment(fieldInfo.field, method, object, value);
                            }
                        } else if (fieldInfo.fieldClass == AtomicLong.class) {
                            AtomicLong atomic = (AtomicLong) method.invoke(object);
                            if (atomic != null) {
                                atomic.set(((AtomicLong) value).get());
                            } else {
                                degradeValueAssignment(fieldInfo.field, method, object, value);
                            }
                        } else if (fieldInfo.fieldClass == AtomicBoolean.class) {
                            AtomicBoolean atomic = (AtomicBoolean) method.invoke(object);
                            if (atomic != null) {
                                atomic.set(((AtomicBoolean) value).get());
                            } else {
                                degradeValueAssignment(fieldInfo.field, method, object, value);
                            }
                        } else if (Map.class.isAssignableFrom(method.getReturnType())) {
                            Map map = null;
                            try {
                                map = (Map) method.invoke(object);
                            } catch (InvocationTargetException e) {
                                degradeValueAssignment(fieldInfo.field, method, object, value);
                                return;
                            }
                            if (map != null) {
                                if (map == Collections.emptyMap()) {
                                    return;
                                }
    
                                if (map.isEmpty() && ((Map) value).isEmpty()) {
                                    return;
                                }
    
                                String mapClassName = map.getClass().getName();
                                if (mapClassName.equals("java.util.ImmutableCollections$Map1")
                                        || mapClassName.equals("java.util.ImmutableCollections$MapN")
                                        || mapClassName.startsWith("java.util.Collections$Unmodifiable")) {
                                    // skip
    
                                    return;
                                }
    
                                if (map.getClass().getName().equals("kotlin.collections.EmptyMap")) {
                                    degradeValueAssignment(fieldInfo.field, method, object, value);
                                    return;
                                }
    
                                map.putAll((Map) value);
                            } else if (value != null) {
                                degradeValueAssignment(fieldInfo.field, method, object, value);
                            }
                        } else {
                            Collection collection = null;
                            try {
                                collection = (Collection) method.invoke(object);
                            } catch (InvocationTargetException e) {
                                degradeValueAssignment(fieldInfo.field, method, object, value);
                                return;
                            }
                            if (collection != null && value != null) {
                                String collectionClassName = collection.getClass().getName();
    
                                if (collection == Collections.emptySet()
                                        || collection == Collections.emptyList()
                                        || collectionClassName == "java.util.ImmutableCollections$ListN"
                                        || collectionClassName == "java.util.ImmutableCollections$List12"
                                        || collectionClassName.startsWith("java.util.Collections$Unmodifiable")) {
                                    // skip
                                    return;
                                }
    
                                if (!collection.isEmpty()) {
                                    collection.clear();
                                } else if (((Collection) value).isEmpty()) {
                                    return; //skip
                                }
    
    
                                if (collectionClassName.equals("kotlin.collections.EmptyList")
                                        || collectionClassName.equals("kotlin.collections.EmptySet")) {
                                    degradeValueAssignment(fieldInfo.field, method, object, value);
                                    return;
                                }
                                collection.addAll((Collection) value);
                            } else if (collection == null && value != null) {
                                degradeValueAssignment(fieldInfo.field, method, object, value);
                            }
                        }
                    } else {
                        method.invoke(object, value);
                    }
                } else {
                    final Field field = fieldInfo.field;
                    
                    if (fieldInfo.getOnly) {
                        if (fieldInfo.fieldClass == AtomicInteger.class) {
                            AtomicInteger atomic = (AtomicInteger) field.get(object);
                            if (atomic != null) {
                                atomic.set(((AtomicInteger) value).get());
                            }
                        } else if (fieldInfo.fieldClass == AtomicLong.class) {
                            AtomicLong atomic = (AtomicLong) field.get(object);
                            if (atomic != null) {
                                atomic.set(((AtomicLong) value).get());
                            }
                        } else if (fieldInfo.fieldClass == AtomicBoolean.class) {
                            AtomicBoolean atomic = (AtomicBoolean) field.get(object);
                            if (atomic != null) {
                                atomic.set(((AtomicBoolean) value).get());
                            }
                        } else if (Map.class.isAssignableFrom(fieldInfo.fieldClass)) {
                            Map map = (Map) field.get(object);
                            if (map != null) {
                                if (map == Collections.emptyMap()
                                        || map.getClass().getName().startsWith("java.util.Collections$Unmodifiable")) {
                                    // skip
                                    return;
                                }
                                map.putAll((Map) value);
                            }
                        } else {
                            Collection collection = (Collection) field.get(object);
                            if (collection != null && value != null) {
                                if (collection == Collections.emptySet()
                                        || collection == Collections.emptyList()
                                        || collection.getClass().getName().startsWith("java.util.Collections$Unmodifiable")) {
                                    // skip
                                    return;
                                }
    
                                collection.clear();
                                collection.addAll((Collection) value);
                            }
                        }
                    } else {
                        if (field != null) {
                            field.set(object, value);
                        }
                    }
                }
            } catch (Exception e) {
                throw new JSONException("set property error, " + clazz.getName() + "#" + fieldInfo.name, e);
            }
        }
    
    • 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
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164

    在这里插入图片描述
    首次进行setValue的是json中_id的值对应的fieldInfo的fileId却是id,到最后使用fieldInfo的method也就是Student.setId(java.lang.String);通过执行setId就将_id的值赋值给了student中的id属性,为什么_id对应的fieldInfo是id呢?

    • com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch(java.lang.String, int[])
      通过json中的key和int[]获取当前key的FieldDeserializer,FieldDeserializer中有个属性fieldInfo,这个属性就决定了json中key对应的值赋值给对象中的那个属性.
    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++) {
                        hashArray[i] = sortedFieldDeserializers[i].fieldInfo.nameHashCode;
                    }
                    Arrays.sort(hashArray);
                    this.smartMatchHashArray = hashArray;
                }
    
                // smartMatchHashArrayMapping
                long smartKeyHash = TypeUtils.fnv1a_64_lower(key);
                int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
                if (pos < 0) {
                    long smartKeyHash1 = TypeUtils.fnv1a_64_extract(key);
                    pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash1);
                }
    
                boolean is = false;
                if (pos < 0 && (is = key.startsWith("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

    匹配哈希数组映射

                long smartKeyHash = TypeUtils.fnv1a_64_lower(key);
                int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
                if (pos < 0) {
                    long smartKeyHash1 = TypeUtils.fnv1a_64_extract(key);
                    pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash1);
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    先调用TypeUtils.fnv1a_64_lower参数传入key也就是_id获取smartKeyHash ,然后检查smartKeyHash是否存在,也就是当前key再对象中是否有对应的属性;_id肯定是没有的,会进入到if逻辑里面,然后调用TypeUtils.fnv1a_64_extract参数传入的还是key(_id)获取smartKeyHash1,然后检查smartKeyHash1是否是否存在,也就是当前key再对象中是否有对应的属性;这次却能通过检查,主要原因就是因为TypeUtils中的两个方法

    • com.alibaba.fastjson.util.TypeUtils#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
    • com.alibaba.fastjson.util.TypeUtils#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

    上面两个方法相似度是比较高的,唯一不同的是fnv1a_64_extract再对key循环的过程中过滤掉了_和-字符,这样的话相当于将_id变成了id所以返回的FieldDeserializer是id对应的FieldDeserializer,所以到最后会将json中_id的值赋值给对象中的id
    等到json中真正的id返回的就是null而不是id对应的FieldDeserializer,因为setFlags的int数组,当我们对一个属性进行赋值以后,会把setFlags数组中这个属性对应的位置设置为1(之前是0),所以等到真正的id进来就不会再进行赋值了,为了证明这里直接使用代码验证一下

        public static void main(String[] args) {
            // 赋值时_id先进去赋值给student的id,id不会赋值
            String json = "{\"_id\":\"1\",\"id\":\"2\"}";
            Student student = JSON.parseObject(json, Student.class);
            // 1
            System.out.println(student.getId());
            // 赋值时id先进去赋值给student的id,_id不会赋值
            String json1 = "{\"id\":\"2\",\"_id\":\"1\"}";
            Student student1 = JSON.parseObject(json1, Student.class);
            // 2
            System.out.println(student1.getId());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    解决方法

    添加Feature.DisableFieldSmartMatch参数

        public static void main(String[] args) {
            String json = "{\"_id\":\"1\",\"id\":\"2\"}";
            Student student = JSON.parseObject(json, Student.class, Feature.DisableFieldSmartMatch);
            // 2
            System.out.println(student.getId());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用这种方式将不会使用com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch(java.lang.String, int[])来获取key对应的FieldDeserializer,转而会使用com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#getFieldDeserializer(java.lang.String)来获取key对应的FieldDeserializer,有兴趣的可以自己了解下

  • 相关阅读:
    对大数据的批量导入MySQL数据库
    怎么批量把图片转文字?教你几招轻松完成
    C嘎嘎之类和对象上
    typescript里面一个问号(?)两个问号(??)一个感叹号(!)两个感叹号(!!)的用法和区别。
    浏览器运行机制
    国庆day4
    golang将pcm格式音频转为mp3格式
    Rust编写Windows服务
    面试题-5
    FAQ (Kubernetes the hard way)
  • 原文地址:https://blog.csdn.net/qq_43135259/article/details/127617916