链式调用方式重写父类set方法导致fastjson解析的对象属性未赋值

反序列化后发现属性为null

昨天使用fastjsonJSON.parseArray(json, class)反序列化的时候,发现返回的list列表中的对象某个属性为null,但是json字符串里面这个属性是有值的,经过排查发现是因为子类重写了父类的set方法,且返回值类型是父类导致的。
几个解决方法

  • 不重写方法,不重写就肯定没这个问题了
  • 重写方法的返回值类型改成自身,这个是因为fastjson代码处理了返回值不是自身或者void的情况,所以改成自身就好了,推荐这种。
  • 私有属性改成public,这个肯定也不太好
  • 先转成JSONArray对象,再通过其他类似toMap转换,这种不推荐

对照试验

为了做一个对比,写两组代码,一组子类不重写父类的set方法,另外一组重写父类的set方法。为了减少代码量,使用lombok的注解,Accessors(chain = true)代表链式调用。Parent类有xy两个属性,Children类继承Parent类,有一个z属性。
构造一个json数组,里面有两个对象,然后用fastjson解析。

[{"x":"x值","y":"y值","z":"z值"},{"x":"x值2","y":"y值2","z":"z值2"}]

不重写set方法的代码

子类不重写父类的set方法,将json字符串解析成Children集合,运行代码看输出,xyz属性都有值

import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.util.List;

public class JsonParseTest {
    public static void main(String[] args) {
        String jsonText = "[{\"x\":\"x值\",\"y\":\"y值\",\"z\":\"z值\"},{\"x\":\"x值2\",\"y\":\"y值2\",\"z\":\"z值2\"}]";
        List<Children> oldRuleDetailList = JSON.parseArray(jsonText, Children.class);
        System.out.println(oldRuleDetailList);
    }
}

@Setter
@Getter
@ToString
@Accessors(chain = true)
class Parent {
    private String x;

    private String y;
}

@Setter
@Getter
@ToString(callSuper = true)
@Accessors(chain = true)
class Children extends Parent {
    private String z;
}

运行截图:
在这里插入图片描述
因为是链式调用方式,所以set系列方法返回值都是this。比如Parent
在这里插入图片描述

重写父类set方法的代码

为了模拟昨天的情况,在Children类中重写父类的setX方法,返回值类型还是Parent类。运行代码发现,x属性值为null,其他属性值正常。运行结果说明昨天遇到的问题就是因为我在子类里面重写了父类的set方法导致。
昨天出现这个问题之后,我是把那个方法返回值类型改成了Children类,但当时并不知道为什么改了就有值,不改就没有值。

@Setter
@Getter
@ToString(callSuper = true)
@Accessors(chain = true)
class Children extends Parent {
    private String z;

    /**
     * 重写父类的setX方法
     */
    @Override
    public Parent setX(String x) {
        return super.setX(x);
    }
}

运行截图:
在这里插入图片描述

debug源码排查

为了排查问题,拿重写父类set方法的代码进行debug。

  1. 首先调用JSON.parseArray方法解析json字符串,传入json字符串和ChildrenClass对象
    在这里插入图片描述
  2. JSONparseArray方法会创建一个list集合,然后调用DefaultJSONParser类的parseArray方法来解析
    在这里插入图片描述
  3. DefaultJSONParser里面的parseArray方法里面,会根据要传入的class类型,创建不同的反序列化工具对象。
    在这里插入图片描述
  4. 调用getDeserializer方法获取反序列化工具对象,里面的get方法会从缓存里面拿,因为是第一次执行,所以拿到的是null,就会往后执行,最后调用重载的同名方法。在重载的方法里面,会进行各种判断,比如是不是枚举类,是不是特定的java类等等。因为我们是自定义的类,所以最后会调用createJavaBeanDeserializer方法。
    在这里插入图片描述
    在这里插入图片描述
  5. createJavaBeanDeserializer方法里面判断要不要使用asm,因为父类不是public声明的,所以不使用asm,最后是创建一个JavaBeanDeserializerjavaBean反序列化工具类对象并返回。
    在这里插入图片描述
    在这里插入图片描述
  6. JavaBeanDeserializer构造方法中,会调用JavaBeanInfo.build方法,build方法里面会拿到传入类的所有非私有方法(里面会包含父类的非私有方法),然后循环遍历这个方法数组进行处理,找出setXXX相关的方法。它主要就是根据setXXX后面的XXX来确定是什么属性的。最重要的就是在循环方法数组的里面会判断这个要不要继续处理,比如静态方法就跳出。前面遇到的问题正是因为这个循环体里面的一个判断导致的。
    在这里插入图片描述
    在这里插入图片描述
    710行的代码,判断了方法返回值类型,如果是void或者自身,就往下执行,否则跳过,这就是为什么重写了父类的set方法,返回值类型是父类x就没有值,改成自己就有值的原因了。
    在这里插入图片描述
    fieldList对象里面只有yz属性
    在这里插入图片描述
posted @ 2025-01-04 16:53  西瓜当冬瓜  阅读(7)  评论(0)    收藏  举报  来源