ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

关于fastjson的知识又增加了

2021-04-24 17:58:40  阅读:213  来源: 互联网

标签:fastjson userName 知识 userId 关于 test model 序列化 1001


本周有一个需求,需要调用第三方的阿里云接口,对方要求的协议参数,必须首字母大写。而通常情况下,我们定义Bean的时候,不会直接将变量名设置为大写开头,这样不符合编码规范,那有什么办法可以将首字母序列化为大写的字符串,作为请求参数传递呢?这里主要通过FastJson的一些定制化行为,完成了该类需求。同时,在这个过程中,顺便阅读了一些fastjson的源码,特此记录一下。

序列化

@Data
public static class Model {
  private int userId;
  private String userName;
}

使用代码验证一下默认的序列化行为。

Model model = new Model();
model.userId = 1001;
model.userName = "test";
System.out.println(JSON.toJSONString(model));

输出结果:

{"userId":1001,"userName":"test"}

可以看到默认的序列化行为是驼峰形式。

那如果要实现首字母大写的序列化形式,要如何操作呢?

方案1 序列化时指定配置

Model model = new Model();
model.userId = 1001;
model.userName = "test";
// 生产环境中,config需要设置为singleton处理,不然会存在性能问题
SerializeConfig serializeConfig = new SerializeConfig();
serializeConfig.propertyNamingStrategy = PropertyNamingStrategy.PascalCase;
String text = JSON.toJSONString(model, serializeConfig, SerializerFeature.SortField);

对于PropertyNamingStrategy的行为可以参照FastJson的issue https://github.com/alibaba/fastjson/wiki/PropertyNamingStrategy_cn

输出结果:

{"UserId":1001,"UserName":"test"}
方案2 使用JSONField注解指定字段级别的配置
@Data
public static class ModelOne {
  @JSONField(name = "UserId")
  private int userId;
  @JSONField(name = "UserName")
  private String userName;
}

测试代码:

ModelOne model = new ModelOne();
model.userId = 1001;
model.userName = "test";
String text = JSON.toJSONString(model, SerializerFeature.SortField);
System.out.println(text);

输出结果:

{"UserId":1001,"UserName":"test"}
方案3 使用JSONType注解指定类级别的配置
@Data
@JSONType(naming = PropertyNamingStrategy.PascalCase)
public static class ModelTwo {
  private int userId;
  private String userName;
}

测试代码:

ModelTwo model = new ModelTwo();
model.userId = 1001;
model.userName = "test";
String text = JSON.toJSONString(model, SerializerFeature.SortField);
System.out.println(text);

输出结果:

{"UserId":1001,"UserName":"test"}
当JSONType和JSONField并行使用时的行为
@Data
@JSONType(naming = PropertyNamingStrategy.PascalCase)
public static class ModelThree {
  private int userId;
  @JSONField(name = "userName")
  private String userName;
}

测试代码:

ModelThree model = new ModelThree();
model.userId = 1001;
model.userName = "test";
String text = JSON.toJSONString(model, SerializerFeature.SortField);
System.out.println(text);

输出示例:

{"UserId":1001,"userName":"test"}

可以看到,如果两者共同使用时,会以字段上的JSONField为主。

反序列化

看了序列化之后,那反序列化是否类似呢。

我们的输入字符串均是:

{\"UserId\":1001, \"UserName\":\"test\"}

断言验证:

Assert.assertEquals(1001, model2.userId);
Assert.assertEquals("test", model2.userName);
默认反序列化
@Data
public static class Model {
  private int userId;
  private String userName;
}

Model model2 = JSON.parseObject("{\"UserId\":1001, \"UserName\":\"test\"}", Model.class);
Assert.assertEquals(1001, model2.userId);
Assert.assertEquals("test", model2.userName);
反序列化时指定配置
@Data
public static class ModelZero {
  private int userId;
  private String userName;
}

// 生成环境,需要设置为singleton,不然会存在性能问题
ParserConfig parserConfig = new ParserConfig();
parserConfig.propertyNamingStrategy = PropertyNamingStrategy.PascalCase;
Model model2 = JSON.parseObject("{\"UserId\":1001, \"UserName\":\"test\"}", Model.class);
Assert.assertEquals(1001, model2.userId);
Assert.assertEquals("test", model2.userName);
// 测试通过
使用了JSONField配置后,进行反序列化
@Data
public static class ModelOne {
  @JSONField(name = "UserId")
  private int userId;
  @JSONField(name = "UserName")
  private String userName;
}

ModelOne model2 = JSON.parseObject("{\"UserId\":1001, \"UserName\":\"test\"}", ModelOne.class);
Assert.assertEquals(1001, model2.userId);
Assert.assertEquals("test", model2.userName);
使用JSONType配置后,进行反序列化
@Data
@JSONType(naming = PropertyNamingStrategy.PascalCase)
public static class ModelTwo {
  private int userId;
  private String userName;
}

ModelTwo model2 = JSON.parseObject("{\"UserId\":1001, \"UserName\":\"test\"}", ModelTwo.class);
Assert.assertEquals(1001, model2.userId);
Assert.assertEquals("test", model2.userName);
配合使用JSONType和JSONField

@Data
@JSONType(naming = PropertyNamingStrategy.PascalCase)
public static class ModelThree {
  private int userId;
  @JSONField(name = "userName")
  private String userName;
}

ModelThree model2 = JSON.parseObject("{\"UserId\":1001, \"UserName\":\"test\"}", ModelThree.class);
Assert.assertEquals(1001, model2.userId);
Assert.assertEquals("test", model2.userName);

通过跑Test case发现,上述的反序列化验证代码,竟然全部通过了,对于使用了单独使用了JSONField和单独使用了JSONType的行为非常容易理解,因为输入字符串和自己的指定是一致的。但是为什么默认情况下,包括通过@JSONField将字段配置改为小写的情况下,还可以正常序列化呢?

关于FastJson的smartMatch

上述问题的核心在于FastJson的smartMatch,也就是说fastJson的智能检测,将UserId映射到了userId,UserName映射到了userName。此时就需要去源码中进行验证了,这一段的核心源码在JavaBeanDeserializer 方法 public FieldDeserializer smartMatch(String key, int[] setFlags)中。

对该段源码解析一下:

public FieldDeserializer smartMatch(String key, int[] setFlags) {
  // key: 输入的字段名称,比如UserName或者UserId
  if (key == null) {
    return null;
  }
	
  // 先从正常的字段反序列化器中寻找,正常的字段反序列化器中存在的是以 uesrId和userName驼峰式命名的,所以在当输入是UserId或者UserName首字母大写时,无法找到正确的反序列化器。
  FieldDeserializer fieldDeserializer = getFieldDeserializer(key, setFlags);

  if (fieldDeserializer == null) {
    if (this.smartMatchHashArray == null) {
      // 基于已有的正常的序列化器,生成一个智能匹配array
      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
    // 这里是智能匹配的核心代码,先根据下面的TypeUtils.fnval_64_lower计算key的hash值
    //public static long fnv1a_64_lower(String key){
    //    long hashCode = 0xcbf29ce484222325L;
    //    for(int i = 0; i < key.length(); ++i){
    //        char ch = key.charAt(i);
    //        这里是关键,这里会将大写字母转为小写字母,所以userName和UserName计算得到的hash值是一样的
    //        if(ch >= 'A' && ch <= 'Z'){
    //            ch = (char) (ch + 32);
    //        }
    //        hashCode ^= ch;
    //        hashCode *= 0x100000001b3L;
    //    }
    //    return hashCode;
    // }
    // 先将大写字母转为小写计算
    long smartKeyHash = TypeUtils.fnv1a_64_lower(key);
    // 在已有的反序列化器的key中寻找对应的位置
    int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
    if (pos < 0) {
      // 如果没有找到,则需要看一下是否存在下划线或者中划线
      // public static long fnv1a_64_extract(String key){
      // long hashCode = 0xcbf29ce484222325L;
      //   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 *= 0x100000001b3L;
      //  }
      //  return hashCode;
      //}
      long smartKeyHash1 = TypeUtils.fnv1a_64_extract(key);
      pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash1);
    }

    boolean is = false;
    if (pos < 0 && (is = key.startsWith("is"))) {
      // 对于boolean类型的,通常的get方法是以is开头的,需要特殊处理
      smartKeyHash = TypeUtils.fnv1a_64_extract(key.substring(2));
      pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash);
    }

    if (pos >= 0) {
      // 如果根据hash找到了正常反序列化器的hash位置
      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) {
            // 赋值,p位置对应的key hash,应该使用i位置的反序列化器
            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;
      // 这里很关键,如果设置了disalbeFieldSmartMatch,就直接返回null了
      if ((fieldInfo.parserFeatures & Feature.DisableFieldSmartMatch.mask) != 0) {
        return null;
      }

      Class fieldClass = fieldInfo.fieldClass;
      if (is && (fieldClass != boolean.class && fieldClass != Boolean.class)) {
        // 如果是以is,但是类型又不是boolean类型,返回null
        fieldDeserializer = null;
      }
    }
  }


  return fieldDeserializer;
}

分析完源码,我们可以通过设置Feature.DisableFieldSmartMatch来看看是否可以解决我们的疑问。

我们对默认的行为进行验证,另外一种情况类似

ModelZero model2 = JSON.parseObject("{\"UserId\":1001, \"UserName\":\"test\"}", ModelZero.class, Feature.DisableFieldSmartMatch);
System.out.println(JSON.toJSONString(model2));

输出结果:

// userName没有映射上,所以是null,没有输出。
// userId也没有映射上,是int的默认值,为0
{"userId":0}

总结

  1. 可以使用SerializeConfig和ParserConfig在执行操作的时候,定制行为。但是一定要注意,在生产环境,将其设置为singleton。
  2. 可以使用@JSONField进行字段级别的序列化和反序列化配置。
  3. 可以使用@JSONType进行类级别的序列化和反序列化配置,优先级低于@JSONField
  4. 默认情况下,在进行反序列化时,FastJson会进行smartMatch,会屏蔽大小写,下划线和中划线的差异。也就是userName等价于UserName等价于user-name。可以通过序列化时,指定Feature.DisableFieldSmartMatch关闭此特性.

标签:fastjson,userName,知识,userId,关于,test,model,序列化,1001
来源: https://blog.csdn.net/yi_chao_jiang/article/details/116098408

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有