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

复现

定义 MyClass
publicclassMyClass
{


// boolean 类型的属性
privateboolean
 isActive;

privateboolean
 valid;


// int 类型的属性
privateint
 id;


// 默认构造器
publicMyClass()
{

    }


// 带有所有属性的构造器
publicMyClass(boolean isActive, boolean valid, int id)
{

this
.isActive = isActive;

this
.valid = valid;

this
.id = id;

    }


// isActive 的 getter 和 setter 方法
publicbooleanisActive()
{

return
 isActive;

    }


publicvoidsetActive(boolean isActive)
{

this
.isActive = isActive;

    }


// valid 的 getter 和 setter 方法
publicbooleangetValid()
{

return
 valid;

    }


publicvoidsetValid(boolean valid)
{

this
.valid = valid;

    }


// id 的 getter 和 setter 方法
publicintgetId()
{

return
 id;

    }


publicvoidsetId(int id)
{

this
.id = id;

    }

}

编写测试代码:
import
 com.alibaba.fastjson.JSON;


publicclassMyClassMain
{

publicstaticvoidmain(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<?>)
publicfinal ObjectSerializer createJavaBeanSerializer(Class<?> clazz)
{

    String className = clazz.getName();

long
 hashCode64 = TypeUtils.fnv1a_64(className);

if
 (Arrays.binarySearch(denyClasses, hashCode64) >= 
0
) {

thrownew
 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
publicstatic 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 字符串时和实际属性名存在偏差。

解决办法

遵循阿里巴巴 Java 开发手册

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

使用别名

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


@JSONField
( name=
"isActive"
)

// boolean 类型的属性
privateboolean
 isActive;

privateboolean
 valid;


// 省略其他

}

总结

我认为 对于 Java 程序员而言,《阿里巴巴 Java 开发手册》至少读 3 遍。 工作中发现太多常见低级问题都是 《阿里巴巴 Java 开发手册》已经存在的问题。然而推荐很多次《阿里巴巴 Java 开发手册》虽然很薄,但是很多人还是不会认真阅读几遍,导致在相同的地方跌倒很多遍。哪怕遇到类似的问题,也很容易快速想出原因。
我们遇到问题时,一定不要止步于解决问题,而是应该寻找最合理的解决方案。比如虽然加上 @JSONField 可以“解决问题”,但侵入性太强,假如其他人也用这个对象使用其他 JSON 序列化工具,就会出问题,这并不是一个好的方案。
AI 时代,遇到问题自己如果不能快速解决时,可以考虑寻求 AI 的帮助。不过使用 AI 时一定要将问题交代清楚。很多同学说的问题连其他同事都听不懂,更不别说 AI 了。
来源:mingmingruyue.blog.csdn.net/article
/details/131270338


·················END·················
继续阅读
阅读原文