Gson解析泛型

做网络请求的时候肯定要封装回调,我这里就传了泛型,但是出了个问题是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就行了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 在android开发中经常需要从接口服务器获取数据,然后展示在手机界面上。其中手机端和接口服务器之间通常使用jso...
    zizi192阅读 22,900评论 0 5
  • 当用Gson解析泛型会报以下错误: com.google.gson.internal.LinkedTreeMap ...
    格老子阅读 1,635评论 0 1
  • 1.概述2.Gson的目标3.Gson的性能和扩展性4.Gson的使用者5.如何使用Gson 通过Maven来使用...
    人失格阅读 14,167评论 2 18
  • 我们 生活 在 不同的 星球
    高望祖阅读 175评论 0 0
  • 窗外淅淅沥沥的雨声 敲打在我的心房 这一瞬间 脑中的思绪却格外的清晰 过去,现在,未来 我们能做的还有什么呢 也许...
    林加夕d阅读 217评论 0 1