关于Json反序列化泛型对象

978 阅读3分钟

背景

后台请求某个接口,该接口返回的JSON数据中包含data属性,data属性会对应一个java实体,在使用FastJson反序列化的时候发现使用parseObject(String str, Class<T> objectClass)并不能将data正确反序列化成某个java实体,而是直接以JsonObject类型存在,伪代码如下:

请求接口方法

 public class ConfirmationClient {
 ​
   public Result<QrInfoResult> getQrInfo(String keyWord) {
     Result<QrInfoResult> qrInfo = confirmationService.getQrInfo(keyWord);
     // 这里会类型转换的错误:JsonObjec不能转为QrInfoResult
     QrInfoResult data = qrInfo.getData();
     return null;
   }
 }
 ​
 public class ConfirmationService {
   public Result<QrInfoResult> getQrInfo(String txHash) {
 ​
     Asserts.notEmpty(txHash, "证书编号");
 ​
     // 请求接口
     String result = requestApi(QrinfoParam.builder().keyWord(txHash).build(), Constants.genUrl(Constants.URI_CONFIRM_QR));
 ​
     // 序列化
     return JSONObject.parseObject(result, Result.class);
   }
 }

Result类信息

 @Data
 @Builder
 @AllArgsConstructor
 @NoArgsConstructor
 public class Result<T> implements Serializable {
   private static final int SUCCESS_CODE = 0;
 ​
   private Integer code;
   private String message;
   private T data;
   private long timestamp;
 ​
   public boolean success() {
     return this.code == SUCCESS_CODE;
   }
 ​
 }

Result类中data属性对应的实体

 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
 public class QrInfoResult {
 ​
     /**
      * 编号
      */
     private String confirmSeq;
 ​
     /**
      * 时间
      */
     private String confirmTime;
 ​
 }

debug调试图

image-20230112154958542

可以看到,这样方式,data的类型为JSONObject,尝试获取data时:

image-20230112155311118

原因

原因很简单,就是FastJson不知道应该把里边的元素序列化成什么类型只能转成JSONObject(鬼知道你Result类的data是什么玩意)。那为啥返回值是Result,而方法要求的返回值是Result<QrInfoResult>,在编译的时候没有报错呢?其实是因为泛型的类型擦除,Result<QrInfoResult>擦除完后类似于这样Result<Object>,所以编译并没有报错。

解决方法

FastJson提供了相应方法

 public static <T> T parseObject(String str, TypeReference typeReference, com.alibaba.fastjson.parser.Feature... features) {
   return parseObject(str, typeReference.getType(), features);
 }

我们只要把代码做如下调整

 public Result<QrInfoResult> getQrInfo(String txHash) {
 ​
   Asserts.notEmpty(txHash, "证书编号");
 ​
   // 请求接口
   String result = requestApi(QrinfoParam.builder().keyWord(txHash).build(), Constants.genUrl(Constants.URI_CONFIRM_QR));
   
   // 序列化
   return JSONObject.parseObject(result, new TypeReference<Result<QrInfoResult>>() {
   });
 }

debug调试

image-20230112161002141

再深一层

1. 匿名内部子类

 new TypeReference<Result<QrInfoResult>>() {
 }

这种方式乍一看比较陌生,那这个呢

 new Runnable() {
     @Override
     public void run() {
         // ...
     }
 };

是不是好一些了,这种方式是创建了一个匿名内部子类,这个类有几个特点(不全):

  • 必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口
  • 匿名内部类中是不能定义构造函数的

所以new TypeReference<Result<QrInfoResult>>() {}这种方式实际是会创建一个新的类的实例,这个新类是TypeReference的子类。再看FastJson的源码

 protected TypeReference() {
   Type superClass = this.getClass().getGenericSuperclass();
   Type type = ((ParameterizedType)superClass).getActualTypeArguments()[0];
   Type cachedType = (Type)classTypeCache.get(type);
   if (cachedType == null) {
     classTypeCache.putIfAbsent(type, type);
     cachedType = (Type)classTypeCache.get(type);
   }
 ​
   this.type = cachedType;
 }

构造函数是protected修饰的,其他包的类必须要成为其子类才能访问其类成员(protected 修饰的类成员可以被三种类所访问:该类自身、与它在同一个包中的其他类以及在其他包中的该类的子类)。

那创建子类实例的作用是什么呢?是为了能够获取泛型信息

2. 获取泛型信息

 protected TypeReference() {
   Type superClass = this.getClass().getGenericSuperclass();
   Type type = ((ParameterizedType)superClass).getActualTypeArguments()[0];
   Type cachedType = (Type)classTypeCache.get(type);
   if (cachedType == null) {
     classTypeCache.putIfAbsent(type, type);
     cachedType = (Type)classTypeCache.get(type);
   }
 ​
   this.type = cachedType;
 }
  • getGenericSuperclass()

    • 返回此Class所表示的实体(类、接口、基本类型等)的超类Type
  • ((ParameterizedType)superClass)

    • ParameterizedType参数化类型,指的是声明时带有泛型的类型
  • getActualTypeArguments()

    • 返回此类型实际类型参数的Type对象的数组
    • [0]可以获得第一个泛型

deub看下:

image-20230112164647728

那么getGenericSuperclass的泛型信息是如何获取的呢?

class 文件中保存了一个 Signature 属性,里面存储了泛型参数信息, getGenericSuperclass 就是去读取了这里的信息。

Signature 是泛型类型的一个特有属性,存储了源码里声明的泛型类型,在编译期,如果源码里指明了类型的情况就可以存储这个指明的类型,如果没有指明类型,那存储的就是这个泛型擦除后的类型。

那么使用new TypeReference<Result<QrInfoResult>>() {}的方式会生成一个子类,大致如下:

 class ZiTypeReference extends TypeReference<Result<QrInfoResult>>{}

这个类在源码中指明了泛型的信息,即Result<QrInfoResult>,那么就可以通过getGenericSuperclass获取到泛型的真正类型。

所以,其实 getGenericSuperclass 并没有获取运行时类型的魔力,它只是读取了编译期确定并存储在 Class 文件里的泛型类型而已。

参考资料

资料1

资料2