FastJson實(shí)現(xiàn)駝峰下劃線相互轉(zhuǎn)換方法詳解
PropertyNamingStrategy
有四種序列化方式。
CamelCase策略,Java對象屬性:personId,序列化后屬性:persionId – 實(shí)際只改了首字母 大寫變小寫
PascalCase策略,Java對象屬性:personId,序列化后屬性:PersonId – 實(shí)際只改了首字母 小寫變大寫
SnakeCase策略,Java對象屬性:personId,序列化后屬性:person_id --大寫字母前加下劃線
KebabCase策略,Java對象屬性:personId,序列化后屬性:person-id -大寫字母前加減號
public enum PropertyNamingStrategy {
CamelCase, //駝峰
PascalCase, //
SnakeCase, //大寫字母前加下劃線
KebabCase;
public String translate(String propertyName) {
switch (this) {
case SnakeCase: {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < propertyName.length(); ++i) {
char ch = propertyName.charAt(i);
if (ch >= 'A' && ch <= 'Z') {
char ch_ucase = (char) (ch + 32);
if (i > 0) {
buf.append('_');
}
buf.append(ch_ucase);
} else {
buf.append(ch);
}
}
return buf.toString();
}
case KebabCase: {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < propertyName.length(); ++i) {
char ch = propertyName.charAt(i);
if (ch >= 'A' && ch <= 'Z') {
char ch_ucase = (char) (ch + 32);
if (i > 0) {
buf.append('-');
}
buf.append(ch_ucase);
} else {
buf.append(ch);
}
}
return buf.toString();
}
case PascalCase: {
char ch = propertyName.charAt(0);
if (ch >= 'a' && ch <= 'z') {
char[] chars = propertyName.toCharArray();
chars[0] -= 32;
return new String(chars);
}
return propertyName;
}
case CamelCase: {
char ch = propertyName.charAt(0);
if (ch >= 'A' && ch <= 'Z') {
char[] chars = propertyName.toCharArray();
chars[0] += 32;
return new String(chars);
}
return propertyName;
}
default:
return propertyName;
}
}
發(fā)揮作用的是translate方法
指定序列化格式
了解了PropertyNamingStrategy后,看其是怎么發(fā)揮作用的,
閱讀源碼發(fā)現(xiàn)在buildBeanInfo時(shí)(注意是將bean轉(zhuǎn)為json時(shí)構(gòu)建json信息時(shí),如果是map,JSONObject不會有這個(gè)轉(zhuǎn)換)
if(propertyNamingStrategy != null && !fieldAnnotationAndNameExists){
propertyName = propertyNamingStrategy.translate(propertyName);
}
這里分別調(diào)用PropertyNamingStrategy對應(yīng)的方法處理
常見誤區(qū)
那么也就是說通過PropertyNamingStrategy的方式設(shè)置輸出格式,只對javaBean有效,并且,至于轉(zhuǎn)換結(jié)果,需要根據(jù)PropertyNamingStrategy#translate方法的內(nèi)容具體分析
如果javaBean中的字段是用下劃線間隔的,那么指定CamelCase進(jìn)行序列化,也是無法轉(zhuǎn)成駝峰的!
例如
Student student = new Student();
student.setTest_name("test");
SerializeConfig serializeConfig = new SerializeConfig();
serializeConfig.setPropertyNamingStrategy(PropertyNamingStrategy.CamelCase);
System.out.println(JSON.toJSONString(student,serializeConfig));
輸出{test_name":“test”},因?yàn)閳?zhí)行 PropertyNamingStrategy#translate的CamelCase,僅僅只是,判斷如果首字母大寫轉(zhuǎn)成小寫。并不能完成,下劃線到駝峰的轉(zhuǎn)換
case CamelCase: {
char ch = propertyName.charAt(0);
if (ch >= 'A' && ch <= 'Z') {
char[] chars = propertyName.toCharArray();
chars[0] += 32;
return new String(chars);
}
return propertyName;
}
指定反序列化格式
智能匹配功能
fastjson反序列化時(shí),是能自動下劃線轉(zhuǎn)駝峰的。這點(diǎn)是很方便的。,在反序列化時(shí)無論采用那種形式都能匹配成功并設(shè)置值
String str = "{'user_name':123}";
User user = JSON.parseObject(str, User.class);
System.out.println(user);
輸出{userName=‘123’}
fastjson智能匹配處理過程
fastjson在進(jìn)行反序列化的時(shí)候,對每一個(gè)json字段的key值解析時(shí),會調(diào)用
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField
這個(gè)方法

以上面的例子為例,通過debug打個(gè)斷點(diǎn)看一下解析user_id時(shí)的處理邏輯。
此時(shí)這個(gè)方法中的key為user_id,object為要反序列化的結(jié)果對象,這個(gè)例子中就是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 {
//進(jìn)行智能匹配
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;
}
//獲取出反序列化目標(biāo)對象的字段名稱hashcode值,并進(jìn)行排序
Arrays.sort(hashArray);
this.smartMatchHashArray = hashArray;
}
// smartMatchHashArrayMapping
long smartKeyHash = TypeUtils.fnv1a_64_lower(key);
//進(jìn)行二分查找,判斷是否找到
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去掉后再次進(jìn)行匹配
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中之所以能做到下劃線自動轉(zhuǎn)駝峰,主要還是因?yàn)樵谶M(jìn)行字段對比時(shí),使用了fnv1a_64_lower和fnv1a_64_extract方法進(jìn)行了處理。
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;
}
//大寫轉(zhuǎn)小寫
if (ch >= 'A' && ch <= 'Z') {
ch = (char) (ch + 32);
}
hashCode ^= ch;
hashCode *= fnv1a_64_magic_prime;
}
return hashCode;
}
從源碼可以看出,fnv1a_64_extract方法主要做了這個(gè)事:
去掉下劃線、減號,并大寫轉(zhuǎn)小寫
總結(jié)
fastjson中字段智能匹配的原理是在字段匹配時(shí),使用了TypeUtils.fnv1a_64_lower方法對字段進(jìn)行全體轉(zhuǎn)小寫處理。
之后再用TypeUtils.fnv1a_64_extract方法對json字段進(jìn)行去掉"_“和”-"符號,再全體轉(zhuǎn)小寫處理。
如果上面的操作仍然沒有匹配成功,會再進(jìn)行一次去掉json字段中的is再次進(jìn)行匹配。
如果上面的操作仍然沒有匹配成功,會再進(jìn)行一次去掉json字段中的is再次進(jìn)行匹配。
關(guān)閉智能匹配的情況
智能匹配時(shí)默認(rèn)開啟的,需要手動關(guān)閉,看這個(gè)例子
String str = "{'user_name':123}";
ParserConfig parserConfig = new ParserConfig();
parserConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
User user = JSON.parseObject(str, User.class, parserConfig,Feature.DisableFieldSmartMatch);
System.out.println(user);
輸出{userName=‘null’}
那么這種情況如何完成下劃線到駝峰的轉(zhuǎn)換
那么就需要使用parseConfig了
String str = "{'user_name':123}";
ParserConfig parserConfig = new ParserConfig();
parserConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
User user = JSON.parseObject(str, User.class,parserConfig,Feature.DisableFieldSmartMatch);
System.out.println(user);
那么此時(shí)PropertyNamingStrategy.SnakeCase又是如何發(fā)揮作用的?
斷點(diǎn)PropertyNamingStrategy#translate方法
發(fā)現(xiàn)在構(gòu)建JavaBeanDeserializer時(shí)
public JavaBeanDeserializer(ParserConfig config, Class<?> clazz, Type type){
this(config //
, JavaBeanInfo.build(clazz, type, config.propertyNamingStrategy, config.fieldBased, config.compatibleWithJavaBean, config.isJacksonCompatible())
);
}
if (propertyNamingStrategy != null) {
propertyName = propertyNamingStrategy.translate(propertyName);
}
add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures,
annotation, fieldAnnotation, null, genericInfo));
會根據(jù)配置對propertyName進(jìn)行translate。轉(zhuǎn)換成對應(yīng)格式的屬性名稱
常見誤區(qū):
與序列化誤區(qū)相同,如果是map,JSONObject不會有這個(gè)轉(zhuǎn)換,并且轉(zhuǎn)換結(jié)果需要參照translate方方法邏輯來看
值的注意的是,JSONObject的toJavaObject方法,智能匹配會生效??梢苑判牡眠M(jìn)行下劃線和駝峰得互相轉(zhuǎn)換
String str = "{'user_name':123}";
JSONObject object = (JSONObject) JSON.parse(str);
System.out.println(object);
User user = object.toJavaObject(User.class);
System.out.println(user);
到此這篇關(guān)于FastJson實(shí)現(xiàn)駝峰下劃線相互轉(zhuǎn)換方法詳解的文章就介紹到這了,更多相關(guān)FastJson駝峰下劃線相互轉(zhuǎn)換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Quarkus的Spring擴(kuò)展快速改造Spring項(xiàng)目
這篇文章主要為大家介紹了Quarkus的Spring項(xiàng)目擴(kuò)展,帶大家快速改造Spring項(xiàng)目示例演繹,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02
利用SpringMVC接收復(fù)雜對象和多個(gè)文件(前端使用JQuery)
這篇文章主要介紹了利用SpringMVC接收復(fù)雜對象和多個(gè)文件(前端使用JQuery),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10
SpringCloud微服務(wù)熔斷器Hystrix使用詳解
這篇文章主要介紹了Spring Cloud Hyxtrix的基本使用,它是Spring Cloud中集成的一個(gè)組件,在整個(gè)生態(tài)中主要為我們提供服務(wù)隔離,服務(wù)熔斷,服務(wù)降級功能,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
使用MultipartFile實(shí)現(xiàn)文件上傳功能
這篇文章主要介紹了使用MultipartFile實(shí)現(xiàn)文件上傳功能,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06
Java8新特性之JavaFX 8_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java8新特性之JavaFX 8的相關(guān)知識,非常不錯,具有參考借鑒價(jià)值,需要的朋友參考下吧2017-06-06
Springboot指定掃描路徑的實(shí)現(xiàn)示例
本文主要介紹了Springboot指定掃描路徑的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05
一文掌握SpringSecurity?BCrypt密碼加密和解密
BCrypt就是一款加密工具,可以比較方便地實(shí)現(xiàn)數(shù)據(jù)的加密工作。也可以簡單理解為它內(nèi)部自己實(shí)現(xiàn)了隨機(jī)加鹽處理,這篇文章主要介紹了SpringSecurity?BCrypt密碼加密和解密,一文學(xué)會使用BCryptPasswordEncoder的方法,需要的朋友可以參考下2023-04-04

