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呢?(请求失败默认抛异常,不需要每个调用方都进行判断)
其实有两种方法

  1. TypeReference交给外部调用方进行new,那么每次都能精确的获取到泛型参数
  2. 那如果不想调用方那么麻烦,能不能直接用泛型做呢?

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的无参构造函数,可以看到步骤:

  1. 获取父类
  2. 获取父类的泛型参数

有趣的是:获取到的类型竟然是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反序列化泛型对象;泛型是变化的,如何写出通用代码?(源码分析)

无法使用泛型使用 Jackson 反序列化动态 json

Jackson序列化(8)— 支持泛型的反序列化

JSON 序列化与反序列化(-)泛型 及 java.lang.reflect.Type

无法反序列化泛型类型的集合

泛型/JSON JavaScriptSerializer C#