经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JSON » 查看文章
FastJson实现驼峰下划线相互转换方法详解
来源:jb51  时间:2023/1/20 8:39:13  对本文有异议

PropertyNamingStrategy

有四种序列化方式。

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

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

SnakeCase策略,Java对象属性:personId,序列化后属性:person_id --大写字母前加下划线

KebabCase策略,Java对象属性:personId,序列化后属性:person-id -大写字母前加减号

  1. public enum PropertyNamingStrategy {
  2. CamelCase, //驼峰
  3. PascalCase, //
  4. SnakeCase, //大写字母前加下划线
  5. KebabCase;
  6. public String translate(String propertyName) {
  7. switch (this) {
  8. case SnakeCase: {
  9. StringBuilder buf = new StringBuilder();
  10. for (int i = 0; i < propertyName.length(); ++i) {
  11. char ch = propertyName.charAt(i);
  12. if (ch >= 'A' && ch <= 'Z') {
  13. char ch_ucase = (char) (ch + 32);
  14. if (i > 0) {
  15. buf.append('_');
  16. }
  17. buf.append(ch_ucase);
  18. } else {
  19. buf.append(ch);
  20. }
  21. }
  22. return buf.toString();
  23. }
  24. case KebabCase: {
  25. StringBuilder buf = new StringBuilder();
  26. for (int i = 0; i < propertyName.length(); ++i) {
  27. char ch = propertyName.charAt(i);
  28. if (ch >= 'A' && ch <= 'Z') {
  29. char ch_ucase = (char) (ch + 32);
  30. if (i > 0) {
  31. buf.append('-');
  32. }
  33. buf.append(ch_ucase);
  34. } else {
  35. buf.append(ch);
  36. }
  37. }
  38. return buf.toString();
  39. }
  40. case PascalCase: {
  41. char ch = propertyName.charAt(0);
  42. if (ch >= 'a' && ch <= 'z') {
  43. char[] chars = propertyName.toCharArray();
  44. chars[0] -= 32;
  45. return new String(chars);
  46. }
  47. return propertyName;
  48. }
  49. case CamelCase: {
  50. char ch = propertyName.charAt(0);
  51. if (ch >= 'A' && ch <= 'Z') {
  52. char[] chars = propertyName.toCharArray();
  53. chars[0] += 32;
  54. return new String(chars);
  55. }
  56. return propertyName;
  57. }
  58. default:
  59. return propertyName;
  60. }
  61. }

发挥作用的是translate方法

指定序列化格式

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

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

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

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

常见误区

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

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

例如

  1. Student student = new Student();
  2. student.setTest_name("test");
  3. SerializeConfig serializeConfig = new SerializeConfig();
  4. serializeConfig.setPropertyNamingStrategy(PropertyNamingStrategy.CamelCase);
  5. System.out.println(JSON.toJSONString(student,serializeConfig));

输出{test_name":“test”},因为执行 PropertyNamingStrategy#translate的CamelCase,仅仅只是,判断如果首字母大写转成小写。并不能完成,下划线到驼峰的转换

  1. case CamelCase: {
  2. char ch = propertyName.charAt(0);
  3. if (ch >= 'A' && ch <= 'Z') {
  4. char[] chars = propertyName.toCharArray();
  5. chars[0] += 32;
  6. return new String(chars);
  7. }
  8.  
  9. return propertyName;
  10. }

指定反序列化格式

智能匹配功能

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

  1. String str = "{'user_name':123}";
  2. User user = JSON.parseObject(str, User.class);
  3. 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

  1. public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType,
  2. Map<String, Object> fieldValues, int[] setFlags) {
  3. JSONLexer lexer = parser.lexer; // xxx
  4. //是否禁用智能匹配;
  5. final int disableFieldSmartMatchMask = Feature.DisableFieldSmartMatch.mask;
  6. final int initStringFieldAsEmpty = Feature.InitStringFieldAsEmpty.mask;
  7. FieldDeserializer fieldDeserializer;
  8. if (lexer.isEnabled(disableFieldSmartMatchMask) || (this.beanInfo.parserFeatures & disableFieldSmartMatchMask) != 0) {
  9. fieldDeserializer = getFieldDeserializer(key);
  10. } else if (lexer.isEnabled(initStringFieldAsEmpty) || (this.beanInfo.parserFeatures & initStringFieldAsEmpty) != 0) {
  11. fieldDeserializer = smartMatch(key);
  12. } else {
  13. //进行智能匹配
  14. fieldDeserializer = smartMatch(key, setFlags);
  15. }
  16. ***此处省略N多行***
  17. }

再看下核心的代码,智能匹配smartMatch

  1. public FieldDeserializer smartMatch(String key, int[] setFlags) {
  2. if (key == null) {
  3. return null;
  4. }
  5. FieldDeserializer fieldDeserializer = getFieldDeserializer(key, setFlags);
  6. if (fieldDeserializer == null) {
  7. if (this.smartMatchHashArray == null) {
  8. long[] hashArray = new long[sortedFieldDeserializers.length];
  9. for (int i = 0; i < sortedFieldDeserializers.length; i++) {
  10. //java字段的nameHashCode,源码见下方
  11. hashArray[i] = sortedFieldDeserializers[i].fieldInfo.nameHashCode;
  12. }
  13. //获取出反序列化目标对象的字段名称hashcode值,并进行排序
  14. Arrays.sort(hashArray);
  15. this.smartMatchHashArray = hashArray;
  16. }
  17. // smartMatchHashArrayMapping
  18. long smartKeyHash = TypeUtils.fnv1a_64_lower(key);
  19. //进行二分查找,判断是否找到
  20. int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
  21. if (pos < 0) {
  22. //原始字段没有匹配到,用fnv1a_64_extract处理一下再次匹配
  23. long smartKeyHash1 = TypeUtils.fnv1a_64_extract(key);
  24. pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash1);
  25. }
  26. boolean is = false;
  27. if (pos < 0 && (is = key.startsWith("is"))) {
  28. //上面的操作后仍然没有匹配到,把is去掉后再次进行匹配
  29. smartKeyHash = TypeUtils.fnv1a_64_extract(key.substring(2));
  30. pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
  31. }
  32. if (pos >= 0) {
  33. //通过智能匹配字段匹配成功
  34. if (smartMatchHashArrayMapping == null) {
  35. short[] mapping = new short[smartMatchHashArray.length];
  36. Arrays.fill(mapping, (short) -1);
  37. for (int i = 0; i < sortedFieldDeserializers.length; i++) {
  38. int p = Arrays.binarySearch(smartMatchHashArray, sortedFieldDeserializers[i].fieldInfo.nameHashCode);
  39. if (p >= 0) {
  40. mapping[p] = (short) i;
  41. }
  42. }
  43. smartMatchHashArrayMapping = mapping;
  44. }
  45. int deserIndex = smartMatchHashArrayMapping[pos];
  46. if (deserIndex != -1) {
  47. if (!isSetFlag(deserIndex, setFlags)) {
  48. fieldDeserializer = sortedFieldDeserializers[deserIndex];
  49. }
  50. }
  51. }
  52. if (fieldDeserializer != null) {
  53. FieldInfo fieldInfo = fieldDeserializer.fieldInfo;
  54. if ((fieldInfo.parserFeatures & Feature.DisableFieldSmartMatch.mask) != 0) {
  55. return null;
  56. }
  57.  
  58. Class fieldClass = fieldInfo.fieldClass;
  59. if (is && (fieldClass != boolean.class && fieldClass != Boolean.class)) {
  60. fieldDeserializer = null;
  61. }
  62. }
  63. }
  64. return fieldDeserializer;
  65. }

通过上面的smartMatch方法可以看出,fastjson中之所以能做到下划线自动转驼峰,主要还是因为在进行字段对比时,使用了fnv1a_64_lower和fnv1a_64_extract方法进行了处理。

fnv1a_64_extract方法源码:

  1. public static long fnv1a_64_extract(String key) {
  2. long hashCode = fnv1a_64_magic_hashcode;
  3. for (int i = 0; i < key.length(); ++i) {
  4. char ch = key.charAt(i);
  5. //去掉下划线和减号
  6. if (ch == '_' || ch == '-') {
  7. continue;
  8. }
  9. //大写转小写
  10. if (ch >= 'A' && ch <= 'Z') {
  11. ch = (char) (ch + 32);
  12. }
  13. hashCode ^= ch;
  14. hashCode *= fnv1a_64_magic_prime;
  15. }
  16. return hashCode;
  17. }

从源码可以看出,fnv1a_64_extract方法主要做了这个事:

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

总结

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

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

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

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

关闭智能匹配的情况

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

  1. String str = "{'user_name':123}";
  2. ParserConfig parserConfig = new ParserConfig();
  3. parserConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
  4. User user = JSON.parseObject(str, User.class, parserConfig,Feature.DisableFieldSmartMatch);
  5. System.out.println(user);

输出{userName=‘null’}

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

那么就需要使用parseConfig了

  1. String str = "{'user_name':123}";
  2. ParserConfig parserConfig = new ParserConfig();
  3. parserConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
  4. User user = JSON.parseObject(str, User.class,parserConfig,Feature.DisableFieldSmartMatch);
  5. System.out.println(user);

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

断点PropertyNamingStrategy#translate方法

发现在构建JavaBeanDeserializer时

  1. public JavaBeanDeserializer(ParserConfig config, Class<?> clazz, Type type){
  2. this(config //
  3. , JavaBeanInfo.build(clazz, type, config.propertyNamingStrategy, config.fieldBased, config.compatibleWithJavaBean, config.isJacksonCompatible())
  4. );
  5. }
  1. if (propertyNamingStrategy != null) {
  2. propertyName = propertyNamingStrategy.translate(propertyName);
  3. }
  4. add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures,
  5. annotation, fieldAnnotation, null, genericInfo));

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

常见误区:

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

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

  1. String str = "{'user_name':123}";
  2. JSONObject object = (JSONObject) JSON.parse(str);
  3. System.out.println(object);
  4. User user = object.toJavaObject(User.class);
  5. System.out.println(user);

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号