美文网首页Java8知识点json
搞定Gson泛型封装

搞定Gson泛型封装

作者: 怪盗kidou | 来源:发表于2016-09-19 16:37 被阅读26066次

作者: @怪盗kidou
如需转载需在明显位置保留作者信息及原文链接
如果博客中有不恰当之处欢迎留言交流
http://www.jianshu.com/p/d62c2be60617

你真的会用Gson吗?Gson使用指南(一) 的第三节我介绍了在Gson中如何使用泛型来简化我们的类设计,但随之而来引入了一个新的问题:封装。不知道各位有没有想过这样一个问题:每次都要用 new TypeToken<XXX>(){}; 好麻烦,有没有更好的办法?

有更好的办法么?当然有!相信也有不少人自己作了尝试,只是有人欢喜有人愁了,不过没关系,今天我们就来解决这个问题。

约定

1、本文涉及到的json格式

// data 为 object 的情况
{"code":"0","message":"success","data":{}}
// data 为 array 的情况
{"code":"0","message":"success","data":[]}

2、假定第一种的对应的Java类型为 Result<XXX> ,第二种为 Result<List<XXX>>

一、为何封装,如何封装

1、为何封装:
  • new TypeToken<XXX>(){} 麻烦,IDE格式化后还不好看
  • 不同的地方每进行一次 new TypeToken<XXX>(){} 操作都会生成一个新的类
  • 对于任意类XXX都只有两种情况new TypeToken<Result<XXX>>(){}new TypeToken<Result<List<XXX>>>(){}
  • 方便统一管理
2、如何封装

从上面的我们可以知道,最简单的方法就是提供两个方法分别对应data为Array和Object的情况并接收一个参数,即告知XXX的类型,自动将完成new TypeToken<XXX>(){}new TypeToken<Result<List<XXX>>>(){}的过程。

方法原型:

// 处理 data 为 object 的情况
public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {}
// 处理 data 为 array 的情况
public static <T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz){}

二、为何失败?

对于那些尝试着封装过的人可能都这么写过:

public static <T> Result<List<T>> fromJsonArray(Reader reader) {
    Type type = new TypeToken<Result<List<T>>>(){}.getType();
    return GSON.fromJson(reader, type);
}

当然上面的写法肯定是没有办法完成的,虽然代码不会报错,但运行结果肯定是不对的,因为这里的T 其实是一个 TypeVariable,他在运行时并不会变成我们想要的XXX,所以通过TypeToken 得到的 泛型信息只是 "Result<List<T>>"

三、如何解决?

既然TypeToken的作用是用于获取泛型的类,返回的类型为Type,真正的泛型信息就是放在这个Type里面,既然用TypeToken生成会有问题,那我们自己生成Type就行了嘛。

Type是Java中所有类型的父接口,在1.8以前是一个空接口,自1.8起多了个getTypeName()方法,下面有ParameterizedTypeGenericArrayTypeWildcardTypeTypeVariable 几个接口,以及Class类。这几个接口在本次封装过程中只会用到 ParameterizedType ,所以简单说一下:

ParameterizedType 简单说来就是形如“ 类型<> ”的类型,如:Map<String,User>。下面就以 Map<String,User> 为例讲一下里面各个方法的作用。

public interface ParameterizedType extends Type {
     // 返回Map<String,User>里的String和User,所以这里返回[String.class,User.clas]
    Type[] getActualTypeArguments(); 
    // Map<String,User>里的Map,所以返回值是Map.class
    Type getRawType();
    // 用于这个泛型上中包含了内部类的情况,一般返回null
    Type getOwnerType(); 
}

所以,知道了这里需要的泛型是怎么回事,一切都好说了,下面我们来完成之前留下的空方法。

1、实现一个简易的 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;}
}
2、生成Gson需要的泛型
2.1解析data是object的情况
public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {
    Type type = new ParameterizedTypeImpl(Result.class, new Class[]{clazz});
    return GSON.fromJson(reader, type);
}
2.2解析data是array的情况

是Array的情况要比是Object的情况多那么一步。

public static <T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz) {
    // 生成List<T> 中的 List<T>
    Type listType = new ParameterizedTypeImpl(List.class, new Class[]{clazz});
    // 根据List<T>生成完整的Result<List<T>>
    Type type = new ParameterizedTypeImpl(Result.class, new Type[]{listType});
    return GSON.fromJson(reader, type);
}

本次代码较少,不提供源码

虽然这篇博客是以Gson为例,但从上面的内容可以看出实际上和Gson关系不大,主要的内容还是Java的泛型基础,所以这种封装的方法同样适用于其它的框架。

最后借这次机会给安利一个简易的泛型生成库 TypeBuilder ,其最初实现的目的就是让大家快速的生成泛型信息,同时也会作一些参数检查,保证正确性。

用上面的代码给大家举个例子

public static <T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz) {
    Type type = TypeBuilder
            .newInstance(Result.class)
            .beginSubType(List.class)
            .addTypeParam(clazz)
            .endSubType()
            .build();
    return GSON.fromJson(reader, type);
}

public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {
    Type type = TypeBuilder
            .newInstance(Result.class)
            .addTypeParam(clazz)
            .build();
    return GSON.fromJson(reader, type);
}

相关文章

  • Android中泛型的应用(二)

    背景感谢并参考[怪盗kidou]的《搞定Gson泛型封装》 。上一篇《 Java基础--Android中泛型的应用...

  • 搞定Gson泛型封装

    作者: @怪盗kidou 如需转载需在明显位置保留作者信息及原文链接如果博客中有不恰当之处欢迎留言交流http...

  • Gson 泛型封装model

    在项目开发中和服务端使用json数据格式进行交互的时候,通常服务端返回的数据会约定一个数据格式,例如: 我们真正需...

  • Gson解析Json数组遇到的泛型类型擦除问题

    今天,我本来想对Gson做一个小小封装的,但却遇到了 Gson解析Json数组遇到的泛型类型擦除问题,下面对此做下...

  • Gson解析泛型

    做网络请求的时候肯定要封装回调,我这里就传了泛型,但是出了个问题是Gson没办法直接解析泛型,如果直接解析的话,不...

  • Android Gson 解析泛型报错

    当用Gson解析泛型会报以下错误: com.google.gson.internal.LinkedTreeMap ...

  • Gson泛型

    一、为何封装,如何封装 通常我们解析的json格式为 第一种的对应的Java类型为 Result ,第二...

  • Swift 运用协议泛型封装网络层

    Swift 运用协议泛型封装网络层 Swift 运用协议泛型封装网络层

  • Gson解析泛型数据类型

    Gson解析泛型类型:Res Type type = new TypeToken(){}.getTyp...

  • GSON

    需要掌握的知识点 GSON的基本用法 属性重命名 @SerializedName 注解的使用 GSON使用泛型 利...

网友评论

  • liut_2016:赞,纠结了两三年的问题
  • 我素熊猫:厉害了,暂时不是很明白 慢慢消化下。。
  • 就怕是个demo:TypeBuilder这个包用maven,download不下来呢,指教一下呢
    怪盗kidou:@九爷十三年 你可以对照着,github首页的文档哦,
    TypeBuilder.newInstance(PageBean.class). beginSubType(List.class).beginSubType(Map.class). addTypeParam(String.class). addTypeParam(Info.class).endSubType().endSubType().build()
    应该是这样
    就怕是个demo:@怪盗kidou 我是按照这个指示引入的,失败了 应该是被墙了吧,然后还想请教一下,如何转换
    PageBean<List<Map<String, Info>>>这种类似的复杂类型呢
    怪盗kidou:需要配置一个仓库,打开 https://jitpack.io/#ikidou/TypeBuilder/1.0 选maven,里面有配置信息,还有就是看这个网站在你那儿有没有被墙
  • 键盘上的麒麟臂:写得好,用ParameterizedType是一个很优的解决方案,不像网上一大堆写的都是废话
    怪盗kidou:@键盘上的麒麟臂 毕竟TypeToken的内部原理就是这样的
  • 927b2bae1b4f:首先我们解析json时是知道 T是什么(比如User.class)
    User{
    name,sex;
    }
    既然这样,我们可以

    // data 为 object 的情况--->UserResult.class
    {"code":"0","message":"success","data":{“name”:"小明",“sex”:"男"}}

    GSON.fromJson(reader, UserResult.class);
    927b2bae1b4f:我是为 <T>转化成Type 而来,题主已经给出很好的答案啦
  • 7dae6e523694:不错,解决了我一直纠结的问题y( ˙ᴗ. )耶~
  • f3226498206e:不错,学习了。:+1:
  • 02a39153e40b:楼主写的东西确实很有帮助,而且比较难得的是,文章下面的问题大部分都会参与互动,虽然说回答是情分,不回答是本分,就冲这个一定要给个赞。
  • 330e6c788ba3:不吹不黑,感觉有种如沐春风的感觉,不过在写博客的时候还有一些地方有错别字,或者语句不通,希望作者能够在写完之后适当复查一下,不然太浪费这么棒的博文了
  • 81c17c76d562:可以直接用$Gson$Types.newParameterizedTypeWithOwner()吧
  • 小孩程序员:请问,我导入gson了,为啥GSON.from,这个Gson还是导入不了包呢
    怪盗kidou:@小孩程序员 GSON 指的是一个Gson对像的变量
  • Professioner:package com.ueuo.gson;

    public class Result<T> {
    public int code;
    public String message;
    public T data;
    }

    package com.ueuo.gson;

    import com.google.gson.annotations.SerializedName;

    public class User {

    public String name;
    public int age;

    @SerializedName(value = "emailAddress", alternate = {"email", "email_address"})
    public String emailAddress;

    public User(){
    }

    public User(String name, int age){
    this.name = name;
    this.age = age;
    }

    public User(String name, int age, String emailAddress){
    this.name = name;
    this.age = age;
    this.emailAddress = emailAddress;
    }

    }
  • Professioner:package com.ueuo.gson;

    import java.io.Reader;
    import java.io.StringReader;
    import java.lang.reflect.Type;
    import java.util.List;

    import com.google.gson.Gson;

    public class GsonGenericEnclosure {

    static Gson GSON = new Gson();

    public static void main(String[] args) {

    System.out.println("搞定Gson泛型封装");
    System.out.println("http://www.jianshu.com/p/d62c2be60617";);
    System.out.println("作者: @怪盗kidou\n");

    System.out.println("示例<T> Result<T> fromJsonObject(Reader reader, Class<T> clazz)");
    String jsonstring = "{\"code\":\"123\",\"message\":\"msg\",\"data\":{\"name\":\"怪盗kidou\",\"age\":24,\"emailAddress\":\"ikidou_1@example.com\",\"email\":\"ikidou_2@example.com\",\"email_address\":\"ikidou_3@example.com\"}}";
    Result<User> r = fromJsonObject(new StringReader(jsonstring), User.class);
    System.out.println(r.data.name + "; " + r.data.age + "; " + r.data.emailAddress);

    System.out.println("\n示例<T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz)");
    String jsonlist = "{\"code\":\"123\",\"message\":\"msg\",\"data\":[{\"name\":\"怪盗kidou\",\"age\":24,\"emailAddress\":\"ikidou_1@example.com\",\"email\":\"ikidou_2@example.com\",\"email_address\":\"ikidou_3@example.com\"}]}";
    Result<List<User>> r2 = fromJsonArray(new StringReader(jsonlist), User.class);
    List<User> lists = r2.data;
    for (User u : lists) {
    System.out.println(u.name + "; " + u.age + "; " + u.emailAddress);
    }

    }

    public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {
    Type type = new ParameterizedTypeImpl(Result.class, new Class[] { clazz });
    return GSON.fromJson(reader, type);
    }

    public static <T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz) {
    // 生成List<T> 中的 List<T>
    Type listType = new ParameterizedTypeImpl(List.class, new Class[] { clazz });
    // 根据List<T>生成完整的Result<List<T>>
    Type type = new ParameterizedTypeImpl(Result.class, new Type[] { listType });
    return GSON.fromJson(reader, type);
    }

    }
  • midFang:写的不错,感谢博主;已 收 藏~
  • 相互交流:楼主用用了您提供的TypeBuilder-1.0.jar包,混淆通不过,我是这样混淆的
    -dontwarn ikidou.reflect.**
    -keep class ikidou.reflect.** { *; }
    避免jar混淆过后,用这个 Type type = TypeBuilder
    .newInstance(Result.class)
    .addTypeParam(clazz)
    .build();
    Log.e("maxMemory","type -- " + type);
    return new Gson().fromJson(readerJson, type);
    解析不出来,但是能拿到Result这个,通过这个去获取对应的实体类,是空的,我不知道这个应该怎么混淆才不会出错,忘楼主提供解决方案啊
    怪盗kidou:@相互交流 我在没有使用Gson的情况下混淆了(不加任何规则),最后是打印的数据是正常的啊,是不是你的对应的Bean被混淆了
    相互交流: @怪盗kidou 嗯,这个没有混淆的时候确实能拿到对应的对象,json是造的数据,打印出来都是有值的。单独用Gson解析混淆情况下没有问题。您也可以混淆测试一下估计能发现问题
    怪盗kidou:@相互交流 是Log里没有对应的实体类,还是说方法结果,result中取到的值是null,从两个地方排查一下,1、没有混淆之前是正常的么? 2、在混淆的环境下json对应字段是否有值。
  • 你好_平凡: public static <T> BackResult<List<T>> jsonToListObject(String jsonObject,Class<T> clas) {
    //得到list<t>
    Type listType = new ParameterizedTypeImpl(List.class, new Class[]{clas});
    //得到backresult<list<T>>
    Type type = new ParameterizedTypeImpl(BackResult.class, new Type[]{listType});

    BackResult<List<T>> backResult = new Gson().fromJson(jsonObject, type);

    return backResult;
    }
    请教楼主,这样写为什么有报错呢,报错为IllegalArgumentException: Expected a Class, ParameterizedType, or GenericArrayType, but ParameterizedTypeImpl is of type ParameterizedTypeImpl
    你好_平凡:@怪盗kidou 是的,我看出来了,要继承系统的类,我却自己造了一个。谢谢楼主
    怪盗kidou:@月落乌啼li 你的ParameterizedTypeImpl 的父类是不搞错了?
  • 3fd3252944f2:《搞定Gson泛型封装 - 简书》写的挺不错的,已经收藏了。

    源码解析:http://tinyurl.com/ycqn86hj


    aec055493426:写的不错,谢谢博主;已 收 藏~
  • b509ec45f281:LZ流弊
  • 紫木冰枫:在kotlin中根本不存在这个问题。
    M2y:why?
  • 4acfdff2f2e3:如果T是[]数组 ,能用那个fromJsonObject吗? 我试了好像不行,能用[]数组我就不想用List了,例如,Result<List<User>> 我想改成Result<User[]>,就应该用fromJsonObject吧
    怪盗kidou:@4acfdff2f2e3 我试了,没问题,你是用的 fromJsonObject(reader, User[].class)么?
    4acfdff2f2e3:com.google.gson.internal.$Gson$Types$GenericArrayTypeImpl cannot be stored in an array of type java.lang.Class[]
  • 1fa413c6cd23:2.1解析data是object的情况

    public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {
    Type type = new ParameterizedTypeImpl(Result.class, new Class[]{clazz});
    return GSON.fromJson(reader, type);
    }
    这里面的Result还可以再封装吗?我想做个util直接传入参数就可以了返回一个been
    比如 :
    public static <T> T getObject(String jsonData, Class<T> type) {
    T result = GsonInstance.getGson().fromJson(jsonData, type);
    return result;
    }
    该怎么做呢?
    1fa413c6cd23:@怪盗kidou 我意思是把Result也泛型化,不想在这里指定具体的某个类
    怪盗kidou:@王海龙_26eb 你想怎么封装,传个什么参数,你都没说清,:sweat_smile:
    1fa413c6cd23:能不能把这个Result也泛型化
  • zhuguohui:不错不错
  • 朝花夕拾不起来:这些返回的都不是List啊
  • JackChen1024:java8泛型支持类型推断,为何失败中的例子是没有问题的,可以那样封装
    怪盗kidou:我重试了一下,并不能这样写,如果你不用取值的话,看起来一切正常,但一取值暴露了,同时我们可以从Type取出T的值,还是一个TypeVariable,并不能从Type中取出T真正代表的是什么,难到是我姿势不对?
  • bf865eff7a67:sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.make(List.class, new Class[]{String.Class}, null)

    直接用jdk的就好…
    怪盗kidou:@martinho0330gma 使用jvm的有很多,不是所有的jvm都有这个类,比如 android上就是自己的实现
  • 844b9a3a3a68:这边传入Class,但是在我的转换类只有泛型,并没有具体的class类,怎么处理呢?
    public class JsonConvert<T> {

    private String mDataName = null;

    public T parseData(String result) {
    return fromJsonArray(result, T);
    }
    }
    844b9a3a3a68: @有梦想的程序丶猿 这边解析还是需要传递Class,那么意味着还是不能拿来封装泛型解析了?
  • cea083d9875a:// data 为 object 的情况
    {"code":"0","message":"success","data":{}}
    // data 为 array 的情况
    {"code":"1","message":"fail","data":[]}
    楼主,遇到这种情况怎么办,返回数据这样的,成功data是object,失败data是空的array,求解
    怪盗kidou:@蔡泓 让后台过滤这种情况是最有效的了(错误的情况下,把data置为null,或者不要data字段,如是后台有统一处理的话),其它的方法都不是什么好方法
  • b3ac1e9c7829:我特意登录个账号,就是为了给博主点赞的!看了你的几篇文章,写得很用心,太久没有看到这样用心的文章了 thx~
    怪盗kidou:@xiao_xiao_A 过奖过奖:grin:
  • developerHaoz: public static <T> List<T> parseForList(String result, String successKey, String arrKey, Class<T> clazz) {
    List<T> list = new ArrayList<>();
    JSONObject rootJsonObject = null;
    try {
    rootJsonObject = new JSONObject(result);
    if (rootJsonObject.getBoolean(successKey)) {
    JSONArray rootJsonArray = rootJsonObject.getJSONArray(arrKey);
    Gson g = new Gson();
    for (int i = 0; i < rootJsonArray.length(); i++) {
    T t = g.fromJson(rootJsonArray.getJSONObject(i).toString(), clazz);
    list.add(t);
    }
    }
    } catch (JSONException e) {
    e.printStackTrace();
    }
    return list;
    }
    大神觉得本人的这种做法怎样
    ca825e829631:是不错,就是表述还是有点欠缺。http://blog.csdn.net/tsrio/article/details/50558103,这篇文章看起来会更容易懂一些。
    developerHaoz:@怪盗kidou 嗯嗯好,多谢指点
    怪盗kidou:@09ef3072029d 性能差了不少,能直接搞定肯定是最好的,你在生成JSONObject的时候完整的解析了整个json,在用gson解析的时候,有重新生成了json,别的不说,解析了两遍,生成了一遍
  • 团子吃蛋挞:有一个问题…
    现在服务器返回的数据格式是这样的
    {"res":"1","data":{}}
    当res为1时,data是一个jsonobj
    res是其他错误值时,data有可能为空…
    那么解析时…若data为空,就会报错,想问一下怎么指定空值解析为Null呢
    怪盗kidou:@团子吃蛋挞 这种json格式不标准Gson是处理不了的,再说了这是后台的问题,干嘛要你来处理,请不要为后台擦屁股好吗,到头来他们的问题就变成你的问题了,他们明明可以将结果在一个通用的函数处理掉这种情况的
    团子吃蛋挞:@怪盗kidou 是中间那种情况,由于后台接口返回的json数据非常不规范,而且修改牵动太大…导致要在客户端做处理,请问要怎么处理呢?在typeadapter里面的read怎么判断in.nextXXX()的返回值是空还是Null呢?
    怪盗kidou:data为空是个什么鬼,{"res":"0","data":null},还是{"res":"0","data":} ,还是{"res":"0"},如果是中间的情况,那这个Json都是不标准的,第三种是第一种都是一样的,data就是null.
  • 5e825fe052f7:请问博主{"code":"0","message":"success","data":[],"data_2":[]}这种改怎么封装
    怪盗kidou: @Azuina 两个的类型都是不固定的?还是一样的做法嘛,用两个泛型参数
    5e825fe052f7:@怪盗kidou 对啊,就是后台写的非Restful Api格式的数据
    怪盗kidou:data和data2都是数组啊:flushed:
  • 热血沸腾:博主,还有一个问题 GSON.from() 这个GSON是什么?
    Androidwu:GSON是个包,自己去github拿下
  • 热血沸腾:我也有这个情景啊

    服务器返回
    {"code":"0","message":"success","data":{}}

    {"code":"0","message":"success","data":[]}

    我定义的实体是
    private int code
    private string message
    private T data;

    问题一 : 拿到服务器返回的东东,要判断到底是{} 还是 [] ? 然后调用楼主提供的方法?
    问题二: 楼主最后举例那个是什么意思,没看懂
    隔壁家的女孩儿:我想也问问题一,如何判断服务器返回的是{} 还是[ ]呀
    Androidwu:如果做成这个再不怕类型转换出错了
    844b9a3a3a68: @热血沸腾 json对象和json数组的识别,可用异常捕捉。
  • xujunhe:感谢楼主提供这么好的文章 :+1:
    怪盗kidou: @xujunhe 😁 有用就好
  • e0209f793b16:能给个示例说明一下这个封装类怎么用吗?
  • e0209f793b16:看了这篇看晕了,public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) 怎么调用啊, <T> Result<T>是前面定义的 public class Result<T> {
    public int code;
    public String message;
    public T data;
    }的类吗?
  • 孝直:卤煮,Result<List<T>> fromJsonArray方法我试了下解析不了啊,请问您试了嘛?
    34a48c339e37:@孝直 fromJsonArray 应该是用fromJson 才对
    怪盗kidou: @孝直 我肯定试了啊
  • 93bd00a71476:用上了,6
  • 孝直:TypeBuilder 能分享出来么,哥哥
    孝直:@怪盗kidou 笨了笨了
    怪盗kidou:@孝直 上面也有链接地址啊,既可以用gradle和maven也可以直接下载jar
  • eversky:不错,正好要用到
  • 奔跑的笨鸟:我的简书app代码怎么不能滚动了,看不了代码,
    紫木冰枫:用网页,我已经把客户端卸了。
  • 5a6eddffcac1:又有一个问题, TypeBuilder是哪个包中的啊,我的sdk里怎么会没有?
    丶Silence:这个人真有意思,:joy:
    5a6eddffcac1: @怪盗kidou 你推荐了,不给个地址吗?
    怪盗kidou:@四夕一十 这个是我自已经写的库啊

本文标题:搞定Gson泛型封装

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