FastJson实现驼峰下划线相互转换方法详解

目录
  • PropertyNamingStrategy
  • 发挥作用的是translate方法
  • 指定反序列化格式
    • 智能匹配功能
    • fastjson智能匹配处理过程
    • 关闭智能匹配的情况

PropertyNamingStrategy

有四种序列化方式。

CamelCase策略,Java对象属性:personId,序列化后属性:persionId – 实际只改了首字母 大写变小写

PascalCase策略,Java对象属性:personId,序列化后属性:PersonId – 实际只改了首字母 小写变大写

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

发挥作用的是translate方法

指定序列化格式

了解了PropertyNamingStrategy后,看其是怎么发挥作用的,

阅读源码发现在buildBeanInfo时(注意是将bean转为json时构建json信息时,如果是map,JSONObject不会有这个转换)

    if(propertyNamingStrategy != null && !fieldAnnotationAndNameExists){
                    propertyName = propertyNamingStrategy.translate(propertyName);
                }

这里分别调用PropertyNamingStrategy对应的方法处理

常见误区

那么也就是说通过PropertyNamingStrategy的方式设置输出格式,只对javaBean有效,并且,至于转换结果,需要根据PropertyNamingStrategy#translate方法的内容具体分析

如果javaBean中的字段是用下划线间隔的,那么指定CamelCase进行序列化,也是无法转成驼峰的!

例如

        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”},因为执行 PropertyNamingStrategy#translate的CamelCase,仅仅只是,判断如果首字母大写转成小写。并不能完成,下划线到驼峰的转换

 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反序列化时,是能自动下划线转驼峰的。这点是很方便的。,在反序列化时无论采用那种形式都能匹配成功并设置值

        String str = "{'user_name':123}";
        User user = JSON.parseObject(str, User.class);
        System.out.println(user);

输出{userName=‘123’}

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方法主要做了这个事:

去掉下划线、减号,并大写转小写

总结

fastjson中字段智能匹配的原理是在字段匹配时,使用了TypeUtils.fnv1a_64_lower方法对字段进行全体转小写处理。

之后再用TypeUtils.fnv1a_64_extract方法对json字段进行去掉"_“和”-"符号,再全体转小写处理。

如果上面的操作仍然没有匹配成功,会再进行一次去掉json字段中的is再次进行匹配。

如果上面的操作仍然没有匹配成功,会再进行一次去掉json字段中的is再次进行匹配。

关闭智能匹配的情况

智能匹配时默认开启的,需要手动关闭,看这个例子

 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’}

那么这种情况如何完成下划线到驼峰的转换

那么就需要使用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);

那么此时PropertyNamingStrategy.SnakeCase又是如何发挥作用的?

断点PropertyNamingStrategy#translate方法

发现在构建JavaBeanDeserializer时

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

会根据配置对propertyName进行translate。转换成对应格式的属性名称

常见误区:

与序列化误区相同,如果是map,JSONObject不会有这个转换,并且转换结果需要参照translate方方法逻辑来看

值的注意的是,JSONObject的toJavaObject方法,智能匹配会生效。可以放心得进行下划线和驼峰得互相转换

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

到此这篇关于FastJson实现驼峰下划线相互转换方法详解的文章就介绍到这了,更多相关FastJson驼峰下划线相互转换内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 解决fastjson泛型转换报错的解决方法

    错误信息 Exception in thread "main" java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.xh.demo.UserDO 泛性类 @Data public class ResultSetDTO<T> { private Integer totalSize; private Integer count; private List<T

  • FastJson对于JSON格式字符串、JSON对象及JavaBean之间的相互转换操作

    fastJson对于json格式字符串的解析主要用到了一下三个类: JSON:fastJson的解析器,用于JSON格式字符串与JSON对象及javaBean之间的转换. JSONObject:fastJson提供的json对象. JSONArray:fastJson提供json数组对象. 我们可以把JSONObject当成一个Map<String,Object>来看,只是JSONObject提供了更为丰富便捷的方法,方便我们对于对象属性的操作.我们看一下源码. 同样我们可以把JSONArra

  • fastJson泛型如何转换的实现

    引子 现在负责的业务 和 json 打交道比较多, 最近使用fastJson框架 json串转成泛型对象遇到了一个异常 :java.lang.ClassCastException 还原下场景 : 模型Result<T> public class Result<T> { private String msg; private List<T> module; public String getMsg() { return msg; } public void setMsg(

  • FastJson实现驼峰下划线相互转换方法详解

    目录 PropertyNamingStrategy 发挥作用的是translate方法 指定反序列化格式 智能匹配功能 fastjson智能匹配处理过程 关闭智能匹配的情况 PropertyNamingStrategy 有四种序列化方式. CamelCase策略,Java对象属性:personId,序列化后属性:persionId – 实际只改了首字母 大写变小写 PascalCase策略,Java对象属性:personId,序列化后属性:PersonId – 实际只改了首字母 小写变大写 Sn

  • python 函数、变量中单下划线和双下划线的区别详解

    目录 一._func 单下划线开头 --口头私有变量 1.1.在模块中使用单下划线开头 1.2.在类中使用单下划线开头 二.__func 双下划线开头的函数 --私有变量 2.1.在模块中使用双下划线开头 2.2.在类中使用双下划线开头 三.前后都有双下划线 --特殊变量 一._func 单下划线开头 --口头私有变量 1.1.在模块中使用单下划线开头 在Python中,通过单下划线_来实现模块级别的私有化,变量除外.一般约定以单下划线开头的函数为模块私有的,也就是说from moduleNam

  • Python中的单下划线和双下划线使用场景详解

    单下划线 单下划线用作变量 最常见的一种使用场景是作为变量占位符,使用场景明显可以减少代码中多余变量的使用.为了方便理解,_可以看作被丢弃的变量名称,这样做可以让阅读你代码的人知道,这是个不会被使用的变量,e.g.. for _, _, filenames in os.walk(targetDir): print(filenames) for _ in range(100): print('PythonPoint') 在交互解释器比如iPython中,_变量指向交互解释器中最后一次执行语句的返回

  • python时间与Unix时间戳相互转换方法详解

    对于时间数据,如2018-09-25 09:28:59,有时需要与Unix时间戳进行相互的运算,此时就需要对两种形式进行转换,在Python中,转换时需要用到time模块,具体的函数如下: 其中unix_time函数是正常时间转unix时间,local_time是unix时间转正常时间. import time def unix_time(dt): #转换成时间数组 timeArray = time.strptime(dt, "%Y-%m-%d %H:%M:%S") #转换成时间戳 t

  • C语言栈与队列相互实现详解

    目录 一.本章重点 二.队列实现栈 三.栈实现队列 四.解题思路总结 一.本章重点 用两个队列实现栈 用两个栈实现队列 解题思路总结 二.队列实现栈 我们有两个队列: 入栈数据1. 2. 3 可以将数据入队列至队列一或者队列二. 如何出栈? 但出栈要先出1,怎么办? 第一步: 将队列一出队n-1个至队列二. 第二步: pop队列一的最后一个元素. 接下来怎么入栈呢? 将元素入队至不为空的队列. 怎么判断栈空? 队列一和队列二都为空的情况下,栈就是空的. 如何取栈顶元素? 取不为空的队列尾部元素.

  • C#实现数组元素的数据类型转换方法详解

    目录 一.场景假设 二.解决方案 三.问题延伸 四.数组类的静态转换方法 五.刨根问底 一.场景假设 假设有一串字符串如下所示,字符串中的数字之间已用英文状态下的逗号隔开.要求用此字符串中的数字快速生成int类型的数组,且尽可能使用最少的代码量. string str = "1,2,3,4,5,6,7,8,9"; 二.解决方案 我相信大多数同学在拿到这题的时候,一般都会给出以下的解决方案: public int[] String2IntArray(string str) { var s

  • Redis 对比 Memcached 并在 CentOS 下进行安装配置详解

    Redis 是一个开源.支持网络.基于内存.键值对的 Key-Value 数据库,本篇文章主要介绍了Redis 对比 Memcached 并在 CentOS 下进行安装配置详解,有兴趣的可以了解一下. 了解一下 Redis Redis 是一个开源.支持网络.基于内存.键值对的 Key-Value 数据库,使用 ANSI C 编写,并提供多种语言的 API ,它几乎没有上手难度,只需要几分钟我们就能完成安装工作,并让它开始与应用程序顺畅协作.换句话来说,只需投入一小部分时间与精力,大家就能获得立竿

  • 微信小程序本作用域下调用全局JS详解及实例

    微信小程序本作用域下调用全局JS详解 本地wxml文件 <view> app版本:{{version}} </view> 本地js文件 var app; Page({ data:{ }, onLoad:function() { app = getApp(); this.setData({version:app.globalData.appName}); } }) 全局js文件 //app.js App({ globalData:{ appName:"hcoder"

  • Linux 下sudo网络权限详解

    Linux 下sudo网络权限详解 对于设置了网络代理的服务器,在当前用户下执行网络访问没有问题,但通过sudo执行命令时,就会出现"无网络连接"的错误. 背景 对于设置了网络代理的服务器,在当前用户下执行网络访问没有问题,但通过sudo执行命令时,就会出现"无网络连接"的错误. 普通权限下,wget成功. # wget https://github.com --2016-12-08 09:00:43-- https://github.com/ Connecting

  • linux 下的yum命令详解

    yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及SUSE中的Shell前端软件包管理器.基於RPM包管理,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软体包,无须繁琐地一次次下载.安装.yum提供了查找.安装.删除某一个.一组甚至全部软件包的命令,而且命令简洁而又好记. yum的命令形式一般是如下:yum [options] [command] [package ...] 其中的[opt

随机推荐