使用 fastjson 又又又翻车了,莫名其妙多了属性。。

打印 上一主题 下一主题

主题 882|帖子 882|积分 2646

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

定义 MyClass
  1. public class MyClass {
  2.     // boolean 类型的属性
  3.     private boolean isActive;
  4.     private boolean valid;
  5.     // int 类型的属性
  6.     private int id;
  7.     // 默认构造器
  8.     public MyClass() {
  9.     }
  10.     // 带有所有属性的构造器
  11.     public MyClass(boolean isActive, boolean valid, int id) {
  12.         this.isActive = isActive;
  13.         this.valid = valid;
  14.         this.id = id;
  15.     }
  16.     // isActive 的 getter 和 setter 方法
  17.     public boolean isActive() {
  18.         return isActive;
  19.     }
  20.     public void setActive(boolean isActive) {
  21.         this.isActive = isActive;
  22.     }
  23.     // valid 的 getter 和 setter 方法
  24.     public boolean getValid() {
  25.         return valid;
  26.     }
  27.     public void setValid(boolean valid) {
  28.         this.valid = valid;
  29.     }
  30.     // id 的 getter 和 setter 方法
  31.     public int getId() {
  32.         return id;
  33.     }
  34.     public void setId(int id) {
  35.         this.id = id;
  36.     }
  37. }
复制代码
编写测试代码:
  1. import com.alibaba.fastjson.JSON;
  2. public class MyClassMain {
  3.     public static void main(String[] args) {
  4.         // 创建 MyClass 对象
  5.         MyClass myClass = new MyClass(true, false, 123);
  6.         // 使用 fastjson 序列化对象
  7.         String jsonString = JSON.toJSONString(myClass);
  8.         // 打印 JSON 字符串
  9.         System.out.println(jsonString);
  10.     }
  11. }
复制代码
结果:
  1. {“active”:true,“id”:123,“valid”:false}
复制代码
我们发现多了一个 active 属性,少了一个 isActive 属性!
推荐一个开源免费的 Spring Boot 实战项目:
https://github.com/javastacks/spring-boot-best-practice
分析

通过调试可以发现,问题出现在下面这个函数:
  1. com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer(java.lang.Class<?>)
复制代码
  1. public final ObjectSerializer createJavaBeanSerializer(Class<?> clazz) {
  2.     String className = clazz.getName();
  3.     long hashCode64 = TypeUtils.fnv1a_64(className);
  4.     if (Arrays.binarySearch(denyClasses, hashCode64) >= 0) {
  5.         throw new JSONException("not support class : " + className);
  6.     }
  7.     // 关键
  8.     SerializeBeanInfo beanInfo = TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy, fieldBased);
  9.     if (beanInfo.fields.length == 0 && Iterable.class.isAssignableFrom(clazz)) {
  10.         return MiscCodec.instance;
  11.     }
  12.     return createJavaBeanSerializer(beanInfo);
  13. }
复制代码
而 buildBeanInfo 的关键是com.alibaba.fastjson.util.TypeUtils#computeGetters
  1. public static List<FieldInfo> computeGetters(Class<?> clazz, //
  2.                                              JSONType jsonType, //
  3.                                              Map<String,String> aliasMap, //
  4.                                              Map<String,Field> fieldCacheMap, //
  5.                                              boolean sorted, //
  6.                                              PropertyNamingStrategy propertyNamingStrategy //
  7. ){
  8.     // 省略部分代码
  9.         if(methodName.startsWith("is")){
  10.             if(methodName.length() < 3){
  11.                 continue;
  12.             }
  13.             if(returnType != Boolean.TYPE
  14.                     && returnType != Boolean.class){
  15.                 continue;
  16.             }
  17.             char c2 = methodName.charAt(2);
  18.             String propertyName;
  19.             Field field = null;
  20.             if(Character.isUpperCase(c2)){
  21.                 if(compatibleWithJavaBean){
  22.                     propertyName = decapitalize(methodName.substring(2));
  23.                 } else{
  24.                     propertyName = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
  25.                 }
  26.                 // 这里 isActive 的属性名被计算出 active
  27.                 propertyName = getPropertyNameByCompatibleFieldName(fieldCacheMap, methodName, propertyName, 2);
  28.             }
  29.             // 省略其他
  30.              JSONField fieldAnnotation = null;
  31.             if(field != null){
  32.                 fieldAnnotation = TypeUtils.getAnnotation(field, JSONField.class);
  33.                 if(fieldAnnotation != null){
  34.                     if(!fieldAnnotation.serialize()){
  35.                         continue;
  36.                     }
  37.                     ordinal = fieldAnnotation.ordinal();
  38.                     serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
  39.                     parserFeatures = Feature.of(fieldAnnotation.parseFeatures());
  40.                     if(fieldAnnotation.name().length() != 0){
  41.                         //关键: 使用 JSONField 注解设置的 name 替代属性名
  42.                         propertyName = fieldAnnotation.name();
  43.                         if(aliasMap != null){
  44.                             propertyName = aliasMap.get(propertyName);
  45.                             if(propertyName == null){
  46.                                 continue;
  47.                             }
  48.                         }
  49.                     }
  50.                     if(fieldAnnotation.label().length() != 0){
  51.                         label = fieldAnnotation.label();
  52.                     }
  53.                 }
  54.             }
  55.             // 省略部分代码
  56.             FieldInfo fieldInfo = new FieldInfo(propertyName, method, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
  57.                     annotation, fieldAnnotation, label);
  58.             fieldInfoMap.put(propertyName, fieldInfo);
  59.         }
  60.     }
  61.     Field[] fields = clazz.getFields();
  62.     computeFields(clazz, aliasMap, propertyNamingStrategy, fieldInfoMap, fields);
  63.     return getFieldInfos(clazz, sorted, fieldInfoMap);
  64. }
复制代码
其实 fastjson 通过反射虽然有能力识别真实的属性名,但是实际操作时会根据 getter 方法反推出属性名,造成转为 JSON 字符串时和实际属性名存在偏差。
解决办法

遵循阿里巴巴 Java 开发手册

孤尽老师的《Java 开发手册》 中专门强调任何布尔类型的变量都不要加 is 前缀,基本类型布尔属性反向解析时,会误以为不带 is 导致获取不到属性,抛出异常。关注公众号:Java核心技术,回复:手册,可获取高清完整版。

使用别名

使用 fastjson 自带的 @JSONField 注解,不过这种方式 fastjson 的侵入性太强。
  1. public class MyClass {
  2.     @JSONField( name="isActive")
  3.     // boolean 类型的属性
  4.     private boolean isActive;
  5.     private boolean valid;
  6.     // 省略其他
  7. }
复制代码
总结

我认为 对于 Java 程序员而言,《阿里巴巴 Java 开发手册》至少读 3 遍。 工作中发现太多常见低级问题都是 《阿里巴巴 Java 开发手册》已经存在的问题。关注公众号:Java核心技术,回复:手册,可获取高清完整版。
然而推荐很多次《阿里巴巴 Java 开发手册》虽然很薄,但是很多人还是不会认真阅读几遍,导致在相同的地方跌倒很多遍。哪怕遇到类似的问题,也很容易快速想出原因。
我们遇到问题时,一定不要止步于解决问题,而是应该寻找最合理的解决方案。比如虽然加上 @JSONField 可以“解决问题”,但侵入性太强,假如其他人也用这个对象使用其他 JSON 序列化工具,就会出问题,这并不是一个好的方案。
AI 时代,遇到问题自己如果不能快速解决时,可以考虑寻求 AI 的帮助。不过使用 AI 时一定要将问题交代清楚。很多同学说的问题连其他同事都听不懂,更不别说 AI 了。
版权声明:本文为CSDN博主「明明如月学长」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/w605283073/article/details/131270338
近期热文推荐:
1.1,000+ 道 Java面试题及答案整理(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!
5.《Java开发手册(嵩山版)》最新发布,速速下载!
觉得不错,别忘了随手点赞+转发哦!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

怀念夏天

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表