美文网首页
关于Gson泛型解析的解决方案。

关于Gson泛型解析的解决方案。

作者: 844b9a3a3a68 | 来源:发表于2017-03-18 15:14 被阅读5295次

    因为最近在做Gson解析网络数据的时候遇到一个现象,当我们在服务器拿到的Json数据,一般格式都是比较统一的,只是内容有一定的变化,具体事例如下:

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

    所以,我们一般把需要变动的数据模型用泛型表示:

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

    然后在泛型解析的时候出问题了,为了获取T的实际类型,用了如下代码:

    Type type = new TypeToken<Result<List<T>>>() {}.getType();
    T t = new Gson().fromJson(result, trueType);
    

    当然程序并没有崩溃,最后在结果里面打印返回Result数据时,出现了(类转换异常)这个情况,当时我很费解,然后Debug一步一步的走,这时看到了一个惊奇的现象,List下并不是一个我们JavaBean的类型,而是LinkedTreeMap,这是我不禁返回去看上面解析到的Type类型是不是出错了,果然不出所料,竟然返回了Result<List<T>>类型,并不是我们传入的JavaBean类型,这是因为Java的泛型擦除机制,如果不了解的,可以百度一下哈,文章很多。

    其实这里我纠结几个问题,Type type = new TypeToken<Result<List<T>>>() {}.getType();这句代码中,Result<List<T>>是我们指定的类型,既然要给指定类型,那么他存在的意义是什么呢?
    new Gson().fromJson(result, JavaBean.class);
    

    上面这句代码一样可以解析成实体类,既然都必须指明实体类类型,何必用上面代码先获取Type呢?这里我很疑惑。。。

    然后网上找资料,解决方案都大同小异,我这里使用下面这种:

    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;
        }
    }
    
    public static <T> Result<T> fromJsonObject(String reader, Class<T> clazz) {
        Type type = new ParameterizedTypeImpl(Result.class, new Class[]{clazz});
        return new Gson().fromJson(reader, type);
    }
    
    public static <T> Result<List<T>> fromJsonArray(String 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 new Gson().fromJson(reader, type);
    }
    

    这里还是让我有些疑惑,因为这两个解析方法最终还是需要传递实体类的Class,和最初的幻想越来越远了,我一直以为,能在调用的时候填入返回泛型,然后解析的时候根据泛型类型进行解析,果然还是想多了。。。

    所以我这时在网络请求时,返回类型全指定为String,然后在返回结果的时候,再调用以上两个方法来解析成需要的实体类,后来转眼一想,这到最后还不是自己调用方法解析的么,总觉得这样玩失去了意义,好吧,既然不能通过泛型来直接解析实体类,那我干脆就直接传递实体类了:

    我这里因为使用了Rxjava和Retrofit,所以我在网络请求这里写了这个转换类,并加入到请求队列,在返回的String数据后继续.map(new ResultFunc<T>(beanClass))转换成需要的JavaBean。

        //以下是封装的被观察者,没办法为了解析顺利,我这里传递了JavaBean的Class
        public Observable<String> loadString(String url,Class beanClass) {
            return mService
                    .loadString(url)
                    .map(new StringFunc())
                    .map(new ResultFunc<T>(beanClass));
        }
    

    //********************************************以下是转换类*****************************************************

    public class ResultFunc<T> implements Func1<String, Result<T>> {
        Class beanClass;
    
        public ResultFunc(Class beanClass) {
            this.beanClass = beanClass;
        }
    
        @Override
        public Result<T> call(String result) {
           Result<T> t = null;
            try {
                t = (Result<T>) fromJsonObject(result, beanClass);
            } catch (JsonSyntaxException e) {//解析异常,说明是array数组
                t = (Result<T>) fromJsonArray(result, beanClass);
            }
            return t;
        }
    
        public static <T> Result<T> fromJsonObject(String reader, Class<T> clazz) {
            Type type = new ParameterizedTypeImpl(Result.class, new Class[]{clazz});
            return new Gson().fromJson(reader, type);
        }
    
        public static <T> Result<List<T>> fromJsonArray(String 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 new Gson().fromJson(reader, type);
        }
    }
    

    既然传入了指定JavaBean类型,这里就已经可以用你传入的类解析成你Json数据了,所以这里对异常进行了处理,用于在你收到的data是JsonObject和JsonArray的区分,刚开始拿到返回的String数据时,先用JsonObject解析,如果抛出Json异常说明不是JsonObject类型,所以异常产生后,再用JsonArray去解析数据。

    搞了半天,总觉得有些遗憾,不过总比之前稍微要好点吧,因为搞这些本来就没太大必要,因为Retrofit网络接口定义的时候本来就可以指定返回类型,不过折腾折腾总是好。

    相关文章

      网友评论

          本文标题:关于Gson泛型解析的解决方案。

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