JSON反序列化泛型对象;泛型是变化的,如何写出通用代码?(源码分析)
Posted 帅东
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JSON反序列化泛型对象;泛型是变化的,如何写出通用代码?(源码分析)相关的知识,希望对你有一定的参考价值。
本文以fastjson为例,gson等其他序列化工具都类似。
json如何反序列化出带泛型的结果,这个网上应该很多教程,但本文想要实现更高难度的反序列化。比如:泛型参数在变化,怎么写出通用代码?看例2
先看使用
一共三个类,A/B/C,定义放文章最后了
例1:
如果想要序列化带泛型的对象
B<C> bc = JSON.parseObject(bStr, new TypeReference<B<C>>()
);
一行代码就搞定,但原理是什么呢?为什么不能直接用class?
因为java泛型是假的,java对泛型进行了类型擦除。
但是,利用反射却可以做到,反射是可以拿到泛型参数的。
例2:
在我们写框架代码的时候可能会遇见,第一层的对象我已经知道了,想要序列化第二层对象出来。比如:我们业务代码经常封装了responseBody
代码大概类似这样:
"success":true,
"msg":"错误信息",
"data":
假设我们函数定义是直接返回data呢?(请求失败默认抛异常,不需要每个调用方都进行判断)
其实有两种方法
- TypeReference交给外部调用方进行new,那么每次都能精确的获取到泛型参数
- 那如果不想调用方那么麻烦,能不能直接用泛型做呢?
2如下:我们可以这样做么?
private static <T> T getCbyB(String str)
B<T> b = JSON.parseObject(str, new TypeReference<B<T>>()
);
return b.c;
答案是不行的。
原因是什么呢?
源码分析(TypeReference)
protected TypeReference()
Type superClass = getClass().getGenericSuperclass();
Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
Type cachedType = classTypeCache.get(type);
if (cachedType == null)
classTypeCache.putIfAbsent(type, type);
cachedType = classTypeCache.get(type);
this.type = cachedType;
这是TypeReference的无参构造函数,可以看到步骤:
- 获取父类
- 获取父类的泛型参数
有趣的是:获取到的类型竟然是T?泛型T并没有被翻译
原因很简单,刚刚说过了,java是类型擦除的,压根就不会在运行的时候给你解析出T的类型
在深入分析一下:TypeReference是利用了ParameterizedType这个类,里面的两个值 rawType、actualTypeArguments
rawType:当前类的类型
actualTypeArguments:当前类泛型的列表
那么我们就可以构造TypeReference了,但是你会发现TypeReference并没有那么容易扩展,特别是type定义都是final,根本无法修改
public class TypeReference<T>
static ConcurrentMap<Type, Type> classTypeCache
= new ConcurrentHashMap<Type, Type>(16, 0.75f, 1);
protected final Type type;
不过幸运的是,fastjson也提供了另外一个构造函数(GSON就没那么好运了,方法竟然不是protected的,不过也有解决办法)
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 &&
actualIndex < actualTypeArguments.length)
argTypes[i] = actualTypeArguments[actualIndex++];
// fix for openjdk and android env
if (argTypes[i] instanceof GenericArrayType)
argTypes[i] = TypeUtils.checkPrimitiveArray(
(GenericArrayType) argTypes[i]);
// 如果有多层泛型且该泛型已经注明实现的情况下,判断该泛型下一层是否还有泛型
if(argTypes[i] instanceof ParameterizedType)
argTypes[i] = handlerParameterizedType((ParameterizedType) argTypes[i], actualTypeArguments, actualIndex);
Type key = new ParameterizedTypeImpl(argTypes, thisClass, rawType);
Type cachedType = classTypeCache.get(key);
if (cachedType == null)
classTypeCache.putIfAbsent(key, key);
cachedType = classTypeCache.get(key);
type = cachedType;
大致的意思就是,type可以被替换,替换代码如下:
private static <T> T getCbyB(String str, Class<T> tClass)
// TypeReference<A<T>>这里T会被tClass代替,没有什么作用。删除T,也能编译通过,因为java类型擦除,最后才进行类型强转
// A<T> at = JSON.parseObject(str, new TypeReference<A>(tClass) );
// 但是T不能删除,因为TypeReference这个类里面强制要求,如果传Type类型,泛型参数要有两层
// 因为这个方法就是为了让你替换第二层的type,如果没有第二层根本不需要用这个方法
B<T> b = JSON.parseObject(str, new TypeReference<B<T>>(tClass)
);
return b.c;
一行代码就搞定了
调用方:
B<C> b = new B<>();
b.age = 10;
b.c = new C();
b.c.name = "dong";
// 序列化 -> b
String bStr = JSON.toJSONString(b);
System.out.println(bStr);
// 反序列化 -> c
C c = getCbyB(bStr, C.class);
是不是很有意思,这样自己就能指定到底要序列化哪个对象了。
如果是这样的泛型呢?
A<B<C>> abc = JSON.parseObject(aStr, new TypeReference<A<B<C>>>()
);
我还是不要A,只要B<C>
同理:
// 错误做法:a.b这里会被解析成JSONObject,因为new TypeReference<A<T>>(tClass) 只能替换一层
A<T> a = JSON.parseObject(str, new TypeReference<A<T>>(tClass)
);
// 正确做法
ParameterizedTypeImpl parameterizedType = new ParameterizedTypeImpl(new Class[]cClass, null, bClass);
A<T> a = JSON.parseObject(str, new TypeReference<A<T>>(parameterizedType)
);
两层、三层都搞定了,N层也不是问题了
其他类
static class A<T>
public T b;
@Override
public String toString()
return "A" +
"b=" + b +
'';
static class B<T>
public int age;
public T c;
@Override
public String toString()
return "B" +
"age=" + age +
", c=" + c +
'';
static class C
public String name;
@Override
public String toString()
return "C" +
"name='" + name + '\\'' +
'';
以上是关于JSON反序列化泛型对象;泛型是变化的,如何写出通用代码?(源码分析)的主要内容,如果未能解决你的问题,请参考以下文章
JSON反序列化泛型对象;泛型是变化的,如何写出通用代码?(源码分析)