链式调用方式重写父类set方法导致fastjson解析的对象属性未赋值
反序列化后发现属性为null
昨天使用fastjson的JSON.parseArray(json, class)反序列化的时候,发现返回的list列表中的对象某个属性为null,但是json字符串里面这个属性是有值的,经过排查发现是因为子类重写了父类的set方法,且返回值类型是父类导致的。
几个解决方法
- 不重写方法,不重写就肯定没这个问题了
- 重写方法的返回值类型改成自身,这个是因为fastjson代码处理了返回值不是自身或者void的情况,所以改成自身就好了,推荐这种。
- 私有属性改成public,这个肯定也不太好
- 先转成JSONArray对象,再通过其他类似toMap转换,这种不推荐
对照试验
为了做一个对比,写两组代码,一组子类不重写父类的set方法,另外一组重写父类的set方法。为了减少代码量,使用lombok的注解,Accessors(chain = true)代表链式调用。Parent类有x、y两个属性,Children类继承Parent类,有一个z属性。
构造一个json数组,里面有两个对象,然后用fastjson解析。
[{"x":"x值","y":"y值","z":"z值"},{"x":"x值2","y":"y值2","z":"z值2"}]
不重写set方法的代码
子类不重写父类的set方法,将json字符串解析成Children集合,运行代码看输出,x、y、z属性都有值
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。
- 首先调用JSON.parseArray方法解析json字符串,传入json字符串和Children的Class对象
  
- JSON类- parseArray方法会创建一个- list集合,然后调用- DefaultJSONParser类的- parseArray方法来解析
  
- DefaultJSONParser里面的- parseArray方法里面,会根据要传入的- class类型,创建不同的反序列化工具对象。
  
- 调用getDeserializer方法获取反序列化工具对象,里面的get方法会从缓存里面拿,因为是第一次执行,所以拿到的是null,就会往后执行,最后调用重载的同名方法。在重载的方法里面,会进行各种判断,比如是不是枚举类,是不是特定的java类等等。因为我们是自定义的类,所以最后会调用createJavaBeanDeserializer方法。
  
  
- createJavaBeanDeserializer方法里面判断要不要使用- asm,因为父类不是- public声明的,所以不使用- asm,最后是创建一个- JavaBeanDeserializerjavaBean反序列化工具类对象并返回。
  
  
- 在JavaBeanDeserializer构造方法中,会调用JavaBeanInfo.build方法,build方法里面会拿到传入类的所有非私有方法(里面会包含父类的非私有方法),然后循环遍历这个方法数组进行处理,找出setXXX相关的方法。它主要就是根据setXXX后面的XXX来确定是什么属性的。最重要的就是在循环方法数组的里面会判断这个要不要继续处理,比如静态方法就跳出。前面遇到的问题正是因为这个循环体里面的一个判断导致的。
  
  
 710行的代码,判断了方法返回值类型,如果是void或者自身,就往下执行,否则跳过,这就是为什么重写了父类的set方法,返回值类型是父类x就没有值,改成自己就有值的原因了。
  
 fieldList对象里面只有y、z属性
  
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号