fastjson反序列化多层嵌套泛型类与java中的Type类型

在使用springmvc时,我们通常会定义类似这样的通用类与前端进行交互,以便于前端可以做一些统一的处理:

  1. public class Result<T> {

  2.  

    private int ret;

  3.  

    private String msg;

  4.  

    private T data;

  5.  

    // 此处省略getter和setter方法

  6.  

    }

这样的类序列化为json后,js反序列化处理起来毫无压力。但是如果rest接口的消费端就是java呢,java泛型的类型擦除却容易引入一些障碍。

一个反序列化的迭代

先定义一个类,后面的例子会用到:

  1. public class Item {

  2.  

    private String name;

  3.  

    private String value;

  4.  

    // 此处省略getter和setter方法

  5.  

    }

JSON数据:

  1. {

  2.  

    "data":{

  3.  

    "name":"username",

  4.  

    "value":"root"

  5.  

    },

  6.  

    "msg":"Success",

  7.  

    "ret":0

  8.  

    }

当拿到上面的数据时,我们想到其对应的类型是Result<Item>,所以得想办法将这个json数据反序列化为这个类型才行。

v1

JSONObject.parseObject(json, Result<Item>.class);,编译器就报错了Cannot select parameterized type

v2

JSONObject.parseObject(json, Result.class);,执行没问题。但是没有Item类型信息,fastjson不可能跟你心有灵犀一点通知道该把data转为Item类型,result.getData().getClass()结果是com.alibaba.fastjson.JSONObject,也算是个妥善处理吧。

v3

找了一下前人的经验,使用TypeReference来处理,JSONObject.parseObject(json, new TypeReference<Result<Item>>(){});,终于“完美”解决!

v4

有了v3的经验,以为找到了通用处理的捷径,遂封装了一个处理这种类型的工具方法:

  1. private static <T> Result<T> parseResultV1(String json) {

  2.  

    return JSONObject.parseObject(json, new TypeReference<Result<T>>() {

  3.  

    });

  4.  

    }

并且把采用v3的地方改用了此parseResult方法:

Result<Item> result = parseResultV1(json);

以为万事大吉,连测都没测试就把代码提交了。测都不测试,当然难以有好结果了:

  1. System.out.println(result.getData());

  2.  

    // java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to Item

很显然parseResultV1把Item的类型信息丢掉了。

  1. {

  2.  

    "data":"Hello,world!",

  3.  

    "msg":"Success",

  4.  

    "ret":0

  5.  

    }

试了一下Result形式的,parseResultV1可以成功将其反序列化。推测(没有看fastjson具体实现)是fastjson刚好检测到data字段就是String类型,并将其赋值到data字段上了。仔细看parseObject并没有报错,而是在getData()时报错的,联系到java的泛型擦除,我们在用getData(),应该把data当作Object类型这么看:

  1. String data = (String)result.getData();

  2.  

    System.out.println(data);

v5

原来TypeReference的构造器是可以传入参数的,

  1. private static <T> Result<T> parseResultV2(String json, Class<T> clazz) {

  2.  

    return JSONObject.parseObject(json, new TypeReference<Result<T>>(clazz) {

  3.  

    });

  4.  

    }

这个可以真的可以完美反序列化Result<Item>了。

v6

后来发现parseResultV2无法处理类似Result<List<T>>,原来TypeReference无法处理嵌套的泛型(这里指的是类型参数未确定,而不是类似Result<List<Item>>类型参数已经确定)。借用Fastjson解析多级泛型的几种方式—使用class文件来解析多级泛型里的方法,新增加一个专门处理List类型的方法:

  1. private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {

  2.  

    return JSONObject.parseObject(json, buildType(Result.class, List.class, Item.class));

  3.  

    }

  4.  

  5.  

    private static Type buildType(Type... types) {

  6.  

    ParameterizedTypeImpl beforeType = null;

  7.  

    if (types != null && types.length > 0) {

  8.  

    for (int i = types.length - 1; i > 0; i--) {

  9.  

    beforeType = new ParameterizedTypeImpl(new Type[]{beforeType == null ? types[i] : beforeType}, null, types[i - 1]);

  10.  

    }

  11.  

    }

  12.  

    return beforeType;

  13.  

    }

或者根据这里只有两层,简单如下:

  1. private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {

  2.  

    ParameterizedTypeImpl inner = new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class);

  3.  

    ParameterizedTypeImpl outer = new ParameterizedTypeImpl(new Type[]{inner}, null, Result.class);

  4.  

    return JSONObject.parseObject(json, outer);

  5.  

    }

v7

todo: 上面两个方法已经可以满足现有需要,有时间再看看能否将两个方法统一为一个。

com.alibaba.fastjson.TypeReference

看看TypeReference的源码:

  1. protected TypeReference(Type... actualTypeArguments) {

  2.  

    Class<?> thisClass = this.getClass();

  3.  

    Type superClass = thisClass.getGenericSuperclass();

  4.  

    ParameterizedType argType = (ParameterizedType)((ParameterizedType)superClass).getActualTypeArguments()[0];

  5.  

    Type rawType = argType.getRawType();

  6.  

    Type[] argTypes = argType.getActualTypeArguments();

  7.  

    int actualIndex = 0;

  8.  

  9.  

    for(int i = 0; i < argTypes.length; ++i) {

  10.  

    if (argTypes[i] instanceof TypeVariable) {

  11.  

    argTypes[i] = actualTypeArguments[actualIndex++];

  12.  

    if (actualIndex >= actualTypeArguments.length) {

  13.  

    break;

  14.  

    }

  15.  

    }

  16.  

    }

  17.  

  18.  

    Type key = new ParameterizedTypeImpl(argTypes, thisClass, rawType);

  19.  

    Type cachedType = (Type)classTypeCache.get(key);

  20.  

    if (cachedType == null) {

  21.  

    classTypeCache.putIfAbsent(key, key);

  22.  

    cachedType = (Type)classTypeCache.get(key);

  23.  

    }

  24.  

  25.  

    this.type = cachedType;

  26.  

    }

实际上它首先获取到了泛型的类型参数argTypes,然后遍历这些类型参数,如果遇到是TypeVariable类型的则用构造函数传入的Type将其替换,然后此处理后的argTypes基于ParameterizedTypeImpl构造出一个新的Type,这样的新的Type就可以具备我们期待的Type的各个泛型类型参数的信息了。所以fastjson就能够符合我们期望地反序列化出了Result<Item>

正是由于这个处理逻辑,所以对于v6里的Result<List<T>>就无法处理了,它只能处理单层多类型参数的情况,而无法处理嵌套的泛型参数。

没找到TypeReference的有参构造函数用法的比较正式的文档,但是基于源码的认识,我们应该这么使用TypeReference的有参构造函数:

  1. new TypeReference<Map<T1, T2>>(clazz1, clazz2){}

  2.  

    new TypeReference<Xxx<T1, T2, T3>>(clazz1, clazz2, clazz3){}

也就是构造器里的Type列表要与泛型类型参数一一对应。

com.alibaba.fastjson.util.ParameterizedTypeImpl

那至于ParameterizedTypeImpl怎么回事呢?

  1. import java.lang.reflect.ParameterizedType;

  2.  

    // ...其他省略...

  3.  

  4.  

    public class ParameterizedTypeImpl implements ParameterizedType {

  5.  

    public ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType){

  6.  

    this.actualTypeArguments = actualTypeArguments;

  7.  

    this.ownerType = ownerType;

  8.  

    this.rawType = rawType;

  9.  

    }

  10.  

    // ...其他省略...

  11.  

    }

以前也没了解过ParameterizedType,与它相关的还有

  1. Type

  2.  

    所有已知子接口:

  3.  

    GenericArrayType, ParameterizedType, TypeVariable<D>, WildcardType

  4.  

    所有已知实现类:

  5.  

    Class

先看看这次已经用到的ParameterizedType接口(下列注释是从jdk中文文档拷贝过来,不太好理解)

  1. public interface ParameterizedType extends Type {

  2.  

    //返回表示此类型实际类型参数的 Type 对象的数组。

  3.  

    //注意,在某些情况下,返回的数组为空。如果此类型表示嵌套在参数化类型中的非参数化类型,则会发生这种情况。

  4.  

    Type[] getActualTypeArguments();

  5.  

    //返回 Type 对象,表示此类型是其成员之一的类型。

  6.  

    Type getOwnerType();

  7.  

    //返回 Type 对象,表示声明此类型的类或接口。

  8.  

    Type getRawType();

  9.  

    }

结合ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType)的例子来理解:
new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class)用于构造List<T>

关于Type

泛型是Java SE 1.5的新特性,Type也是1.5才有的。它是在java加入泛型之后为了扩充类型引入的。与Type相关的一些类或者接口来表示与Class类似但是又因泛型擦除丢失的一些类型信息。

转帖:https://www.cnblogs.com/liqipeng/p/9148545.html

原文地址:https://www.cnblogs.com/ceshi2016/p/11880220.html

时间: 2024-07-31 05:30:29

fastjson反序列化多层嵌套泛型类与java中的Type类型的相关文章

fastjson反序列化LocalDateTime失败的问题java.time.format.DateTimeParseException: Text &#39;2019-05-24 13:52:11&#39; could not be parsed at index 10

本地java类 import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; /*** * 问题处理记录 */ public class UserIssueProcessDto { /*** * 处理说明 */ private String f_clsm; /*** * 施工队处理反馈 */ private String f_fk; /*** * 处理开始时间 */ @Da

java中的null类型---有关null的9件事

摘自 https://blog.csdn.net/qq_25077777/article/details/80174763 今天听到一个问题,java中的null类型,null竟然是一种类型 java语言中有两种类型,一种是基本类型,还有一种是引用类型.还有一个特殊的null类型即表达式null的类型,它没有名字. 因为null类型没有名字,所以不可能声明为null类型的变量或者转换为null类型. null引用是null类型表达式唯一可能的值. null引用可以转换为任意引用类型. 实际上,程

java 中的String类型

java 中的String类型   (1)String类型的数据可以表示所有的数据类型. (2)String中的字符串常量与一般的字符串:                String str0 = "hello";//字符串常量“hello”被预先放到了数据段的字符串常量池中                String str1 = "hello";//直接从常量池中寻找已有的字符串常量                String str2 = new String

1 Java中的时间类型

总结:sql中的时间转 util的时间直接赋值即可:反过来,必须先吧util下的时间转换成毫秒,再通过sql的构造器生成sql的时间格式. 1 Java中的时间类型 java.sql包下给出三个与数据库相关的日期时间类型,分别是: l Date:表示日期,只有年月日,没有时分秒.会丢失时间: l Time:表示时间,只有时分秒,没有年月日.会丢失日期: l Timestamp:表示时间戳,有年月日时分秒,以及毫秒. 这三个类都是java.util.Date的子类. 2 时间类型相互转换 把数据库

Java中primitive type的线程安全性

Java中primite type,如char,integer,bool之类的,它们的读写操作都是atomic的,但是有几个例外: long和double类型不是atomic的,因为long和double都是8字节的,而在32位的CPU上,其机器字长为32位,操作8个字节需要多个指令操作. ++i或者i++,因为要先读后写,也是多步操作. 这些情况下,需要使用AutomicInteger,AutomicLong. 同时,java中的reference的读写也是automic的,虽然referen

详解java中的byte类型

Java也提供了一个byte数据类型,并且是基本类型.java byte是做为最小的数字来处理的,因此它的值域被定义为-128~127,也就是signed byte.下面这篇文章主要给大家介绍了关于java中byte类型的相关资料,需要的朋友可以参考下. 介绍 byte,即字节,由8位的二进制组成.在Java中,byte类型的数据是8位带符号的二进制数. 在计算机中,8位带符号二进制数的取值范围是[-128, 127],所以在Java中,byte类型的取值范围也是[-128, 127]. 取值范

MySql数据库类型bit等与JAVA中的对应类型【布尔类型怎么存】

用char(1):可以表示字符或者数字,但是不能直接计算同列的值.存储消耗1个字节 用tinyint:只能表示数字,可以直接计算,存储消耗2个字节 用bit: 只能表示0或1,不能计算,存储消耗小于等于一个字节. 总结: 如果无扩展需求,仅仅表示2值逻辑的话, Bit 绝对是首选 如果有扩展需求,以后可能多余2个值,就用 tinyint char不考虑,写代码时要带单引号,超麻烦 转: MySql数据库类型bit等与JAVA中的对应类型 2018年12月04日 16:43:58 Ming3394

JAVA中的char类型

1.JAVA中,char占2字节,16位. 2.char赋值 char a='a';  //任意单个字符,加单引号. char a='中';//任意单个中文字,加单引号. char a=111;//整数.0~65535.十进制.八进制.十六进制均可.

Java中的枚举类型

枚举类型可以限定只能取特定值中的某一个. 这个是在编译器就可以限定的. 记住一个原则,错误越早发现越好,尽量在编译器发现. 枚举使用enum(小写)关键字 是java.lang.Enum类型,since jdk1.5 语法 public enum MyColor { red, green, blue };//限定了,用户如果要使用MyColor类型的话,就只能呢使用red,green或者blue三者之一. { MyColor m = MyColor.red; switch(m) {//枚举类型的