本文以fastjson为例,gson等其他序列化工具都类似。
json如何反序列化出带泛型的结果,这个网上应该很多教程,但本文想要实现更高难度的反序列化。比如:泛型参数在变化,怎么写出通用代码?看例2
一共三个类,A/B/C,定义放文章最后了
如果想要序列化带泛型的对象
B<C> bc = JSON.parseObject(bStr, new TypeReference<B<C>>() {
});
一行代码就搞定,但原理是什么呢?为什么不能直接用class?
因为java泛型是假的,java对泛型进行了类型擦除。
但是,利用反射却可以做到,反射是可以拿到泛型参数的。
在我们写框架代码的时候可能会遇见,第一层的对象我已经知道了,想要序列化第二层对象出来。比如:我们业务代码经常封装了responseBody
代码大概类似这样:
{
"success":true,
"msg":"错误信息",
"data":{
}
}
假设我们函数定义是直接返回data呢?(请求失败默认抛异常,不需要每个调用方都进行判断)
其实有两种方法
2如下:我们可以这样做么?
private static <T> T getCbyB(String str) {
B<T> b = JSON.parseObject(str, new TypeReference<B<T>>() {
});
return b.c;
}
答案是不行的。
原因是什么呢?
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>这里T会被tClass代替,没有什么作用。删除T,也能编译通过,因为java类型擦除,最后才进行类型强转
// A at = JSON.parseObject(str, new TypeReference(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
同理:
// 错误做法:a.b这里会被解析成JSONObject,因为new TypeReference>(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 + '\'' +
'}';
}
}