JSON反序列化泛型对象;泛型是变化的,如何写出通用代码?(源码分析)

本文探讨了如何在Java中使用Fastjson进行泛型对象的反序列化,解释了由于类型擦除导致的泛型在运行时无法直接获取的问题。通过分析Fastjson的TypeReference源码,展示了如何利用反射和ParameterizedType来解决这个问题,特别是在处理多层泛型时的技巧。示例代码展示了如何在不明确具体泛型类型的情况下,动态指定反序列化的类。
摘要由CSDN通过智能技术生成

本文以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 + '\'' +
                    '}';
        }
    }
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值