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

0.171字数 928阅读 6344

因为最近在做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网络接口定义的时候本来就可以指定返回类型,不过折腾折腾总是好。

推荐阅读更多精彩内容