Gson解析泛型

作者: 键盘上的麒麟臂 | 来源:发表于2018-07-27 19:00 被阅读26次

    做网络请求的时候肯定要封装回调,我这里就传了泛型,但是出了个问题是Gson没办法直接解析泛型,如果直接解析的话,不会得到一个javabean而是得到一个LinkedTreeMap。
    然后我去网上找了很就,都没有直接能把LinkedTreeMap转成javabean的方法,但是我们不可能给每个请求的结果都单独去写一个解析,这时候我们就不得不去正面一个问题:如何使用Gson来解析泛型
    而我这篇文章都会去围绕这个问题去讲,表面上讲gson,实际上是讲java的type

    因为是分2天写的,前面有点乱,可以直接看总结

    一.使用Gson来解析JSON

    我们先看看平时如何使用Gson来解析json,就假设有个Test类吧。

    Test  test  = gson.fromJson(json, Test.class);
    

    一般在明确对象类型之后我们确实可以这样做,但是如果是泛型呢

    T test  = gson.fromJson(json, T.class);
    

    肯定不能这样玩,这不符合泛型的思想,而且也没有T.class,所以需要换种方法来做



    这个方法的第二个参数是传一个Type,我们可以来看看什么是Type



    都知道能理解成是一个类型的接口,但是从include这句话可以更加清楚的知道他是一个什么意思。

    二.获取Type

    一般你可以很容易的找到这样做

    Type type = new TypeToken<Test>() {}.getType();
    JsonBean jsonBean = gson.fromJson(json, type);
    

    然后你觉得如果解析泛型的话可以这样

    Type type = new TypeToken<T>() {}.getType();
    JsonBean jsonBean = gson.fromJson(json, type);
    

    这个我没试过,但是我知道如果传的不是泛型,而是一个包含泛型的类,最后解析出来的还是LinkedTreeMap,比如这样

    Type type = new TypeToken<Result<T>>() {}.getType();
    JsonBean jsonBean = gson.fromJson(json, type);
    

    因为一般网络请求的数据结构我们都会这样做Result<T>,而这样是没法正常解析出我们想要的对象的。

    这个时候网上就有一篇文章写得特别好
    https://www.jianshu.com/p/d62c2be60617
    可以看到需要重写一个类继承ParameterizedType

    public class ParameterizedTypeImpl implements ParameterizedType {
        private final Class raw;
        private final Type[] args;
        public ParameterizedTypeImpl(Class raw, Type[] args) {
            this.raw = raw;
            this.args = args != null ? args : new Type[0];
        }
        @Override
        public Type[] getActualTypeArguments() {
            return args;
        }
        @Override
        public Type getRawType() {
            return raw;
        }
        @Override
        public Type getOwnerType() {return null;}
    }
    

    然后按照他的写法

    Type type = new ParameterizedTypeImpl(Result.class, new Class[]{clazz});
    

    那么假如我写一个解析类,我要怎么去获取到type,我就假如我自己写的okhttp的回调

    public abstract class HttpCallback<T> implements Callback {
            ........
    }
    

    我要怎么在内部根据泛型来获取到Type对象,然后再根据type解析出javabean,还是假如我们的泛型从外面传进来的是Test

    其实网上有很多代码,我可以一步一步调试分析出对象是什么

    Type type = getClass().getGenericSuperclass();
    

    这个方法也可以获取到Type对象,getClass()是Object类的方法,得到当前类的Class对象,这个很容易懂吧,getGenericSuperclass()是Class类的方法,得到这个Class类对象的Type对象,我们可以调试发现这里的getClass().getGenericSuperclass()为HttpCallback<Test>

    拿到这个对象之后发现发现别人都是这样写的

     Type[] types = ((ParameterizedType)type).getActualTypeArguments();
    

    这里用到了一个ParameterizedType去强转对象,ParameterizedType中其实一共有是三个方法
    (1)getActualTypeArguments()
    (2)getOwnerType()
    (3)getRawType()
    我们来分别看看他们得到的结果
    可以看到getActualTypeArguments得到的一个数组当前只有一个元素types[0] = Test。
    getOwnerType()得到null。
    getRawType()得到HttpCallback

    从这里我们就可以知道,我们需要使用的是getActualTypeArguments()方法来获取我们要的Type
    现在再来想想,我们需要的Type是用泛型转成的Result<Test>
    getActualTypeArguments获得Test,getRawType()获得外层的类型,那把他们拼起来

    public class ParameterizedTypeImpl implements ParameterizedType {
        private final Class raw;
        private final Type[] args;
        public ParameterizedTypeImpl(Class raw, Type[] args) {
            this.raw = raw;
            this.args = args != null ? args : new Type[0];
        }
        @Override
        public Type[] getActualTypeArguments() {
            return args;
        }
        @Override
        public Type getRawType() {
            return raw;
        }
        @Override
        public Type getOwnerType() {return null;}
    }
    

    就能获取到raw<args[0]>的type,那我们要获取Result<Test>,raw就是Result.class,<Test>可以根据((ParameterizedType)type).getActualTypeArguments()来获取。那我们可以这样写

    Class<T> tClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; // 根据当前类获取泛型的Type
    Type ty = new ParameterizedTypeImpl(BaseResponse.class, new Class[]{tClass}); // 传泛型的Type和我们想要的外层类的Type来组装我们想要的类型
    JsonBean jsonBean = gson.fromJson(json, ty);
    

    这样就能正常的解析到我们想要的javabean,注意,这个过程中最主要的是一个组装的思想,用到ParameterizedType 。那有的人问,为什么不可以直接用ParameterizedType 来操作,我们可以看看ParameterizedType 。


    可以看到它是一个接口,继承Type



    为什么要说这个,因为我感觉这个和他Type的一个思想有些关系,要理解别人是怎么去定义这个东西是什么,我们应该怎么去把这个东西说出来。先看getActualTypeArguments的注释
    Returns an array of {@code Type} objects representing the actual type arguments to this type.
    这里有个actual表示真实的,我们获取到的type[]数组内部的元素就是actual,那有必要看看什么是actual,现在我只定义了一个泛型,type[]数组也就只有一个actual,这个actual会得到传进来的泛型的类型,那假如我没有定义泛型和定义两个泛型呢?
    (1)没有定义泛型的情况
    如果没有泛型的话,getClass().getGenericSuperclass()会得到HttpCallBack,((ParameterizedType)type).getActualTypeArguments()会报类型转换错误: java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
    (2)两个泛型的情况
    getClass().getGenericSuperclass()会得到HttpCallBack<String,String>,我外面传的是两个String。
    ((ParameterizedType)type).getActualTypeArguments()得到的数组有两个元素



    (3)当类不是抽象类的时候
    上面的方法都是我在定义的类是抽象类的时候才能起作用,比如我两个参数的类

    那么不是抽象类的时候,getClass().getGenericSuperclass()不管你传多少个参数都会得到Object
    ((ParameterizedType)type).getActualTypeArguments()自然也会报参数错误

    那么如果是List数组的话呢?我们传入的泛型如果是List<T>的话

     Type type = getClass().getGenericSuperclass();
    

    得到HttpCallback<List<Test>>

    Type[] types = ((ParameterizedType)type).getActualTypeArguments();
    

    types[0]得到的是List<Test>
    这样就能把types[0]和我们想要的对象Result传到自定义的ParameterizedType中组装。
    可以看出我们就不需要像之前一样获取Class<T>对象,因为这个对象没法装List,直接获取到Type[] types就行。

    三.总结

    1.gson解析泛型的方法
    Type type = getClass().getGenericSuperclass();
    Type[] types = ((ParameterizedType)type).getActualTypeArguments();
    Type ty = new ParameterizedTypeImpl(Result.class, new Type[]{types[0]});
    Result<T> data = gson.fromJson(josndata, ty);
    
    2.解析时的限制

    (1)type不能转成ParameterizedType的情况
    上面说了没定义泛型的时候type转成ParameterizedType会报类型转换错误,所以要加个判断

    Type type = getClass().getGenericSuperclass();
    if(type instanceof ParameterizedType){
        Type[] types = ((ParameterizedType)type).getActualTypeArguments();
        Type ty = new ParameterizedTypeImpl(Result.class, new Type[]{types[0]});
        Result<T> data = gson.fromJson(josndata, ty);
    }
    

    (2)当前类不为抽象类的话type会得到Object,虽然我们用上面的判断能防止报错,但是这样也无法正常解析出数据,所以我们这个解析泛型的类必须是抽象类。至于为什么只有抽象类才能正常解析出而一般的类的type都拿到是Object,这点我就不是很清楚了。

    3.Type[]数组

    Type[]数组types中的数量是泛型的数量,比如传的泛型是<String,Test>,那这个数组就有两个值
    types[0] = String
    types[1] = Test

    4.传的泛型是一个数组
    public abstract class HttpCallBack<T> {
    
        public HttpCallBack(){
            init();
        }
    
        private void init(){
            Type type = getClass().getGenericSuperclass();
            Type[] types = ((ParameterizedType)type).getActualTypeArguments();
        }
    
    }
    
     new HttpCallBack<List<String>>(){
    
            };
    

    直接贴结果


    5.自定义一个ParameterizedType来拼接类型
    public class ParameterizedTypeImpl implements ParameterizedType {
        private final Class raw;
        private final Type[] args;
        public ParameterizedTypeImpl(Class raw, Type[] args) {
            this.raw = raw;
            this.args = args != null ? args : new Type[0];
        }
        @Override
        public Type[] getActualTypeArguments() {
            return args;
        }
        @Override
        public Type getRawType() {
            return raw;
        }
        @Override
        public Type getOwnerType() {return null;}
    }
    
    
    Type ty = new ParameterizedTypeImpl(Result.class, new Type[]{types[0]});
    

    拿到type之后调gson就行了

    相关文章

      网友评论

        本文标题:Gson解析泛型

        本文链接:https://www.haomeiwen.com/subject/vjlmmftx.html