美文网首页
关于fastjson的知识又增加了

关于fastjson的知识又增加了

作者: 艺超51iwowo | 来源:发表于2021-04-24 17:52 被阅读0次

    本周有一个需求,需要调用第三方的阿里云接口,对方要求的协议参数,必须首字母大写。而通常情况下,我们定义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的知识又增加了

          本文链接:https://www.haomeiwen.com/subject/mqelrltx.html