fastjson反序列化多层嵌套泛型类与java中的Type类型
在使用springmvc时,我们通常会定义类似这样的通用类与前端进行交互,以便于前端可以做一些统一的处理:
public class Result<T> {
private int ret;
private String msg;
private T data;
// 此处省略getter和setter方法
}
这样的类序列化为json后,js反序列化处理起来毫无压力。但是如果rest接口的消费端就是java呢,java泛型的类型擦除却容易引入一些障碍。
一个反序列化的迭代
先定义一个类,后面的例子会用到:
public class Item {
private String name;
private String value;
// 此处省略getter和setter方法
}
JSON数据:
{
"data":{
"name":"username",
"value":"root"
},
"msg":"Success",
"ret":0
}
当拿到上面的数据时,我们想到其对应的类型是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的经验,以为找到了通用处理的捷径,遂封装了一个处理这种类型的工具方法:
private static <T> Result<T> parseResultV1(String json) {
return JSONObject.parseObject(json, new TypeReference<Result<T>>() {
});
}
并且把采用v3的地方改用了此parseResult方法:
Result<Item> result = parseResultV1(json);
以为万事大吉,连测都没测试就把代码提交了。测都不测试,当然难以有好结果了:
System.out.println(result.getData());
// java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to Item
很显然parseResultV1把Item的类型信息丢掉了。
{
"data":"Hello,world!",
"msg":"Success",
"ret":0
}
试了一下Result形式的,parseResultV1可以成功将其反序列化。推测(没有看fastjson具体实现)是fastjson刚好检测到data字段就是String类型,并将其赋值到data字段上了。仔细看parseObject并没有报错,而是在getData()时报错的,联系到java的泛型擦除,我们在用getData(),应该把data当作Object类型这么看:
String data = (String)result.getData();
System.out.println(data);
v5
原来TypeReference的构造器是可以传入参数的,
private static <T> Result<T> parseResultV2(String json, Class<T> clazz) {
return JSONObject.parseObject(json, new TypeReference<Result<T>>(clazz) {
});
}
这个可以真的可以完美反序列化Result<Item>
了。
v6
后来发现parseResultV2无法处理类似Result<List<T>>
,原来TypeReference无法处理嵌套的泛型(这里指的是类型参数未确定,而不是类似Result<List<Item>>
类型参数已经确定)。借用Fastjson解析多级泛型的几种方式—使用class文件来解析多级泛型里的方法,新增加一个专门处理List类型的方法:
private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {
return JSONObject.parseObject(json, buildType(Result.class, List.class, Item.class));
}
private static Type buildType(Type... types) {
ParameterizedTypeImpl beforeType = null;
if (types != null && types.length > 0) {
for (int i = types.length - 1; i > 0; i--) {
beforeType = new ParameterizedTypeImpl(new Type[]{beforeType == null ? types[i] : beforeType}, null, types[i - 1]);
}
}
return beforeType;
}
或者根据这里只有两层,简单如下:
private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) {
ParameterizedTypeImpl inner = new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class);
ParameterizedTypeImpl outer = new ParameterizedTypeImpl(new Type[]{inner}, null, Result.class);
return JSONObject.parseObject(json, outer);
}
v7
todo: 上面两个方法已经可以满足现有需要,有时间再看看能否将两个方法统一为一个。
com.alibaba.fastjson.TypeReference
看看TypeReference的源码:
protected TypeReference(Type... actualTypeArguments) {
Class<?> thisClass = this.getClass();
Type superClass = thisClass.getGenericSuperclass();
ParameterizedType argType = (ParameterizedType)((ParameterizedType)superClass).getActualTypeArguments()[0];
Type rawType = argType.getRawType();
Type[] argTypes = argType.getActualTypeArguments();
int actualIndex = 0;
for(int i = 0; i < argTypes.length; ++i) {
if (argTypes[i] instanceof TypeVariable) {
argTypes[i] = actualTypeArguments[actualIndex++];
if (actualIndex >= actualTypeArguments.length) {
break;
}
}
}
Type key = new ParameterizedTypeImpl(argTypes, thisClass, rawType);
Type cachedType = (Type)classTypeCache.get(key);
if (cachedType == null) {
classTypeCache.putIfAbsent(key, key);
cachedType = (Type)classTypeCache.get(key);
}
this.type = cachedType;
}
实际上它首先获取到了泛型的类型参数argTypes,然后遍历这些类型参数,如果遇到是TypeVariable
类型的则用构造函数传入的Type将其替换,然后此处理后的argTypes基于ParameterizedTypeImpl构造出一个新的Type,这样的新的Type就可以具备我们期待的Type的各个泛型类型参数的信息了。所以fastjson就能够符合我们期望地反序列化出了Result<Item>
。
正是由于这个处理逻辑,所以对于v6里的Result<List<T>>
就无法处理了,它只能处理单层多类型参数的情况,而无法处理嵌套的泛型参数。
没找到TypeReference的有参构造函数用法的比较正式的文档,但是基于源码的认识,我们应该这么使用TypeReference的有参构造函数:
new TypeReference<Map<T1, T2>>(clazz1, clazz2){}
new TypeReference<Xxx<T1, T2, T3>>(clazz1, clazz2, clazz3){}
也就是构造器里的Type列表要与泛型类型参数一一对应。
com.alibaba.fastjson.util.ParameterizedTypeImpl
那至于ParameterizedTypeImpl
怎么回事呢?
import java.lang.reflect.ParameterizedType;
// ...其他省略...
public class ParameterizedTypeImpl implements ParameterizedType {
public ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType){
this.actualTypeArguments = actualTypeArguments;
this.ownerType = ownerType;
this.rawType = rawType;
}
// ...其他省略...
}
以前也没了解过ParameterizedType,与它相关的还有
Type
所有已知子接口:
GenericArrayType, ParameterizedType, TypeVariable<D>, WildcardType
所有已知实现类:
Class
先看看这次已经用到的ParameterizedType接口(下列注释是从jdk中文文档拷贝过来,不太好理解)
public interface ParameterizedType extends Type {
//返回表示此类型实际类型参数的 Type 对象的数组。
//注意,在某些情况下,返回的数组为空。如果此类型表示嵌套在参数化类型中的非参数化类型,则会发生这种情况。
Type[] getActualTypeArguments();
//返回 Type 对象,表示此类型是其成员之一的类型。
Type getOwnerType();
//返回 Type 对象,表示声明此类型的类或接口。
Type getRawType();
}
结合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类似但是又因泛型擦除丢失的一些类型信息。
ParameterizedTypeImpl使用踩坑
fastjson反序列化多层嵌套泛型类与java中的Type类型的更多相关文章
- Java中的Type
Type是Java 编程语言中所有类型的公共高级接口(官方解释),也就是Java中所有类型的“爹”:其中,“所有类型”的描述尤为值得关注.它并不是我们平常工作中经常使用的 int.String.Lis ...
- java中的null类型---有关null的9件事
摘自 https://blog.csdn.net/qq_25077777/article/details/80174763 今天听到一个问题,java中的null类型,null竟然是一种类型 java ...
- Java中primitive type的线程安全性
Java中primite type,如char,integer,bool之类的,它们的读写操作都是atomic的,但是有几个例外: long和double类型不是atomic的,因为long和doub ...
- Java中的Bigdecimal类型运算
Java中的Bigdecimal类型运算 双精度浮点型变量double可以处理16位有效数.在实际应用中,需要对更大或者更小的数进行运算和处理.Java在java.math包中提 供的API类BigD ...
- 理解Java中的字符串类型
1.Java内置对字符串的支持: 所谓的内置支持,即不用像C语言通过char指针实现字符串类型,并且Java的字符串编码是符合Unicode编码标准,这也意味着不用像C++那样通过使用string和w ...
- Java中的枚举类型详解
枚举类型介绍 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中.而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常量的定义 ...
- 关于java中的事件类型
java中的Date是为了证明:天才的程序员也会犯错: java中的Calendar是为了证明:普通的程序员也会犯错. ———————————————————— stackoverflow上大部分都推 ...
- Java中的集合类型的继承关系图
Java中的集合类型的继承关系图
- java中,字符串类型的时间数据怎样转换成date类型。
将字符串类型的时间转换成date类型可以使用SimpleDateFormat来转换,具体方法如下:1.定义一个字符串类型的时间:2.创建一个SimpleDateFormat对象并设置格式:3.最后使用 ...
随机推荐
- nginx中级应用
1.安装监控模块 Nginx中的stub_status模块主要用于查看Nginx的一些状态信息. 本模块默认是不会编译进Nginx的,如果你要使用该模块,则要在编译安装Nginx时指定: . /con ...
- UniCode编码表及部分不可见字符过滤方案
Unicode编码表/0000-0FFF 图例: Unicode 3.1 Unicode 1.0 Unicode 3.2 Unicode 1.1 Unicode 4.0 Unicode 2.0 Uni ...
- django 快捷代码提示
1.右键项目,Mark Directory As Source Root 2.settings配置 3.import包时可忽略app名了
- OO 普通类与静态类的区别
普通类与静态类的区别 普通类与静态类的区别 一.普通类: 1.可以实例化,即可以new; 2.可以继承: 二.静态类:(静态类本质就是 abstract+sealed类) 1.不能被实例化:(抽象的) ...
- 【题解】 UOJ #2. 【NOI2014】起床困难综合症
传送门 不是很简单? 考虑一下这个数的二进制位是什么,要么是1,要么是0. 然后怎么做? 因为一开始可以选0~m的数,那么二进制为中全是0的肯定是可以选的. 接着考虑全是1的怎么选? 如果全都是1的而 ...
- 爬虫开发9.scrapy框架之递归解析和post请求
今日概要 递归爬取解析多页页面数据 scrapy核心组件工作流程 scrapy的post请求发送 今日详情 1.递归爬取解析多页页面数据 - 需求:将糗事百科所有页码的作者和段子内容数据进行爬取切持久 ...
- React Native 搭建开发环境
1.先安装node.js,https://nodejs.org/en/download/ 然后,双击下载好的.msi文件安装即可,安装完成后,打开终端,输出npm -v 即可查看我们刚才安装的node ...
- win10环境下搭建虚拟环境和 virtualenvwrapper-win 使用
1. 安装 virtualenv pip install virtualenv 2. virtualenv基本操作 cd path/dir # 跳转到dir目录 virtualenv env # 在d ...
- c 调用 lua 向lua函数 传递table
参考 https://www.myvoipapp.com/blogs/yxh/2016/07/14/c%E5%90%91lua%E5%87%BD%E6%95%B0%E4%BC%A0%E9%80%92t ...
- 在eclips中配置maven
可参考https://jingyan.baidu.com/article/59703552cb9b988fc00740a4.html