疫情宅在家,研究一下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打个断点看一下解析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再次进行匹配。
再简化一点描述:
- A是java类对象中的一个字段,B是json串的一个字段,A与B的字段进行匹配。
- A的值优先从@JSONField的name值去取,取到的话就用@JSONField.name的小写值。没取到的话直接使用A的值,去掉"-“、”_"并转小写。
- 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);