最近再写代码的时候发现一个JSONObject.parseObject使用的问题,会将json中_id的值赋值给对象id,最后得到一个错误的id,后来百度没有查到这个问题到底怎么解决,只好看下源码找出解决方式.由于业务代码没办法展开,这里写一个简单的Demo来重现一下问题.
@Data
public class Student {
private String id;
}
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());
}
}
我们期望student的id的值应该是2,但是在这里用代码输出的确是1
源码太过复杂不一一展开,只针对为什么会把_id的值赋值给id代码;
对属性进行赋值的关键代码;
@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);
}
}

首次进行setValue的是json中_id的值对应的fieldInfo的fileId却是id,到最后使用fieldInfo的method也就是Student.setId(java.lang.String);通过执行setId就将_id的值赋值给了student中的id属性,为什么_id对应的fieldInfo是id呢?
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;
}
匹配哈希数组映射
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);
}
先调用TypeUtils.fnv1a_64_lower参数传入key也就是_id获取smartKeyHash ,然后检查smartKeyHash是否存在,也就是当前key再对象中是否有对应的属性;_id肯定是没有的,会进入到if逻辑里面,然后调用TypeUtils.fnv1a_64_extract参数传入的还是key(_id)获取smartKeyHash1,然后检查smartKeyHash1是否是否存在,也就是当前key再对象中是否有对应的属性;这次却能通过检查,主要原因就是因为TypeUtils中的两个方法
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;
}
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再对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());
}
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());
}
使用这种方式将不会使用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,有兴趣的可以自己了解下