前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用 fastjson 又又又出现了问题,“莫名其妙”多了属性

使用 fastjson 又又又出现了问题,“莫名其妙”多了属性

作者头像
明明如月学长
发布2023-07-10 17:09:02
2240
发布2023-07-10 17:09:02
举报

一、背景

有一位同事说使用 fastjson 进行 JSON 序列化存储到数据库后,发现 JSON 字符串“莫名其妙地”多了一些属性! 帮看了下代码,看到基本类型的布尔类型以 is 开头的属性,再看到 fastjson ,就有点想笑。

二、复现

定义 MyClass

代码语言:javascript
复制
public class MyClass {

    // boolean 类型的属性
    private boolean isActive;
    private boolean valid;

    // int 类型的属性
    private int id;

    // 默认构造器
    public MyClass() {
    }

    // 带有所有属性的构造器
    public MyClass(boolean isActive, boolean valid, int id) {
        this.isActive = isActive;
        this.valid = valid;
        this.id = id;
    }

    // isActive 的 getter 和 setter 方法
    public boolean isActive() {
        return isActive;
    }

    public void setActive(boolean isActive) {
        this.isActive = isActive;
    }

    // valid 的 getter 和 setter 方法
    public boolean getValid() {
        return valid;
    }

    public void setValid(boolean valid) {
        this.valid = valid;
    }

    // id 的 getter 和 setter 方法
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

编写测试代码:

代码语言:javascript
复制
import com.alibaba.fastjson.JSON;

public class MyClassMain {
    public static void main(String[] args) {
        // 创建 MyClass 对象
        MyClass myClass = new MyClass(true, false, 123);

        // 使用 fastjson 序列化对象
        String jsonString = JSON.toJSONString(myClass);

        // 打印 JSON 字符串
        System.out.println(jsonString);
    }
}

结果:

{“active”:true,“id”:123,“valid”:false}

我们发现多了一个 active 属性,少了一个 isActive 属性!

三、 分析

通过调试可以发现,问题出现在下面这个函数: com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer(java.lang.Class<?>)

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
 public final ObjectSerializer createJavaBeanSerializer(Class<?> clazz) {
        String className = clazz.getName();
        long hashCode64 = TypeUtils.fnv1a_64(className);
	    if (Arrays.binarySearch(denyClasses, hashCode64) >= 0) {
	        throw new JSONException("not support class : " + className);
        }


       // 关键
	    SerializeBeanInfo beanInfo = TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy, fieldBased);
	    if (beanInfo.fields.length == 0 && Iterable.class.isAssignableFrom(clazz)) {
	        return MiscCodec.instance;
	    }

	    return createJavaBeanSerializer(beanInfo);
	}

而 buildBeanInfo 的关键是com.alibaba.fastjson.util.TypeUtils#computeGetters

代码语言:javascript
复制
    public static List<FieldInfo> computeGetters(Class<?> clazz, //
                                                 JSONType jsonType, //
                                                 Map<String,String> aliasMap, //
                                                 Map<String,Field> fieldCacheMap, //
                                                 boolean sorted, //
                                                 PropertyNamingStrategy propertyNamingStrategy //
    ){
        // 省略部分代码

            if(methodName.startsWith("is")){
                if(methodName.length() < 3){
                    continue;
                }
                if(returnType != Boolean.TYPE
                        && returnType != Boolean.class){
                    continue;
                }
                char c2 = methodName.charAt(2);
                String propertyName;
                Field field = null;
                if(Character.isUpperCase(c2)){
                    if(compatibleWithJavaBean){
                        propertyName = decapitalize(methodName.substring(2));
                    } else{
                        propertyName = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
                    }
                    // 这里 isActive 的属性名被计算出 active
                    propertyName = getPropertyNameByCompatibleFieldName(fieldCacheMap, methodName, propertyName, 2);
                } 

                // 省略其他

                 JSONField fieldAnnotation = null;
                if(field != null){
                    fieldAnnotation = TypeUtils.getAnnotation(field, JSONField.class);
                    if(fieldAnnotation != null){
                        if(!fieldAnnotation.serialize()){
                            continue;
                        }
                        ordinal = fieldAnnotation.ordinal();
                        serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                        parserFeatures = Feature.of(fieldAnnotation.parseFeatures());
                        if(fieldAnnotation.name().length() != 0){
                            //关键: 使用 JSONField 注解设置的 name 替代属性名
                            propertyName = fieldAnnotation.name();
                            if(aliasMap != null){
                                propertyName = aliasMap.get(propertyName);
                                if(propertyName == null){
                                    continue;
                                }
                            }
                        }
                        if(fieldAnnotation.label().length() != 0){
                            label = fieldAnnotation.label();
                        }
                    }
                }
                

                // 省略部分代码
             
                FieldInfo fieldInfo = new FieldInfo(propertyName, method, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
                        annotation, fieldAnnotation, label);
                fieldInfoMap.put(propertyName, fieldInfo);
            }
        }
        Field[] fields = clazz.getFields();
        computeFields(clazz, aliasMap, propertyNamingStrategy, fieldInfoMap, fields);
        return getFieldInfos(clazz, sorted, fieldInfoMap);
    }

其实 fastjson 通过反射虽然有能力识别真实的属性名,但是实际操作时会根据 getter 方法反推出属性名,造成转为 JSON 字符串时和实际属性名存在偏差。

四、解决办法

4.1 遵循阿里巴巴 Java 开发手册

孤尽老师的《Java 开发手册》 中专门强调任何布尔类型的变量都不要加 is 前缀,基本类型布尔属性反向解析时,会误以为不带 is 导致获取不到属性,抛出异常。

image.png
image.png

4.2 使用别名

使用 fastjson 自带的 @JSONField 注解,不过这种方式 fastjson 的侵入性太强。

代码语言:javascript
复制
public class MyClass {

    @JSONField( name="isActive")
    // boolean 类型的属性
    private boolean isActive;
    private boolean valid;

    // 省略其他

    
}

五、总结

我认为**对于 Java 程序员而言,《阿里巴巴 Java 开发手册》至少读 3 遍。**工作中发现太多常见低级问题都是 《阿里巴巴 Java 开发手册》已经存在的问题。然而推荐很多次《阿里巴巴 Java 开发手册》虽然很薄,但是很多人还是不会认真阅读几遍,导致在相同的地方跌倒很多遍。哪怕遇到类似的问题,也很容易快速想出原因。

我们遇到问题时,一定不要止步于解决问题,而是应该寻找最合理的解决方案。比如虽然加上 @JSONField 可以“解决问题”,但侵入性太强,假如其他人也用这个对象使用其他 JSON 序列化工具,就会出问题,这并不是一个好的方案。

AI 时代,遇到问题自己如果不能快速解决时,可以考虑寻求 AI 的帮助。不过使用 AI 时一定要将问题交代清楚。很多同学说的问题连其他同事都听不懂,更不别说 AI 了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、复现
  • 三、 分析
  • 四、解决办法
    • 4.1 遵循阿里巴巴 Java 开发手册
      • 4.2 使用别名
      • 五、总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档