使用GSON反序列化泛型类型

69

我在我的Android应用程序中(使用Gson库)实现Json反序列化时遇到了一些问题。

我创建了如下的类:

public class MyJson<T>{
    public List<T> posts;
}

反序列化调用是:

public class JsonDownloader<T> extends AsyncTask<Void, Void, MyJson<T>> {
...
protected MyJson<T> doInBackground(Void... params) {
  ...
    Reader reader = new InputStreamReader(content);
    GsonBuilder gson = new GsonBuilder();
    Type collectionType = new TypeToken<MyJson<T>>() {}.getType();
    result = gson.create().fromJson(reader, collectionType);
  ...
  }
}

问题在于调用后,result.posts列表中只有一个LinkedTreeMap对象数组(具有正确的值,因此问题是反序列化),而不是MyJson对象。当我使用MyObject代替T时,一切都很好,并且MyObject是正确的。
因此,是否有任何方法可以实现反序列化调用而不创建自定义反序列化程序?

2
当我使用MyJson而不是MyJson时,你的意思是什么? - John B
抱歉,我是指MyJson而不是T。 - VizGhar
我很好奇为什么你使用 GsonBuilder 来获取 Gson 实例,而不是直接使用 new Gson.fromJson。这个构造器可能会带来什么好处? - SebasSBM
@SebasSBM GsonBuilder有一个registerTypeAdapter方法,用于实现自定义类型的反序列化器。 - Peter Schorn
6个回答

56
您需要在反序列化时指定T的类型。如果 Gson 不知道要实例化哪种 Type,那么您的List of posts该怎么办呢? 它不能永远保持T。因此,您将提供T作为Class参数。 现在假设posts的类型是String,则您会将MyJson<String>反序列化为下面这样(为简单起见,我还添加了一个String json参数;您可以像以前一样从reader中读取):
doInBackground(String.class, "{posts: [\"article 1\", \"article 2\"]}");

protected MyJson<T> doInBackground(Class<T> type, String json, Void... params) {

    GsonBuilder gson = new GsonBuilder();
    Type collectionType = new TypeToken<MyJson<T>>(){}.getType();

    MyJson<T> myJson = gson.create().fromJson(json, collectionType);

    System.out.println(myJson.getPosts()); // ["article 1", "article 2"]
    return myJson;
}

同样地,要反序列化一个包含Boolean对象的MyJson

doInBackground(Boolean.class, "{posts: [true, false]}");

protected MyJson<T> doInBackground(Class<T> type, String json, Void... params) {

    GsonBuilder gson = new GsonBuilder();
    Type collectionType = new TypeToken<MyJson<T>>(){}.getType();

    MyJson<T> myJson = gson.create().fromJson(json, collectionType);

    System.out.println(myJson.getPosts()); // [true, false]
    return myJson;
}

我在示例中使用了MyJson<T>,以便更好地说明问题。

public class MyJson<T> {

    public List<T> posts;

    public List<T> getPosts() {
        return posts;
    }
}

所以,如果您想要反序列化一个List<MyObject>,您需要调用以下方法:

// assuming no Void parameters were required
MyJson<MyObject> myJson = doInBackground(MyObject.class);

8
Class<T> type 在你的代码中具体用在哪里了? - hoefling
8
不会,因为运行时会进行类型擦除。因此,无论您传递什么类型作为参数,您的函数始终会返回 MyJson<Object> - hoefling
@Mr.Yetti 类型擦除是一个运行时的事情,与这里出现的type参数无关,该参数存在于语句 MyJson<MyObject> myJson = doInBackground(...) 中,以满足方法的泛型返回类型 在编译时。 删除type并查看抛出的编译器错误以理解我的意思。 - Ravi K Thapliyal
你得到了什么确切的错误?doInBackground函数必须在一个泛型类中,例如class JsonDownloader<T>,否则你的代码无论如何都无法编译。因此,type参数并不重要。如果该方法不在泛型类中,则签名<T> MyJson<T> doInBackground(String json, Void... params)足以避免显式转换,但这仍然不能使你免于运行时错误。 - hoefling
我所说的运行时错误是:MyJson<Integer> doInBg = doInBackground(Integer.class, "{posts:['text1','text2']}"); double toDouble = doInBg.getPosts().iterator().next().doubleValue(); System.out.println(toDouble); 你的 type 没有任何作用,你的代码将会失败。 - hoefling
显示剩余2条评论

24

你尝试过吗?

gson.create().fromJson(reader, MyJson.class);

编辑

阅读该帖子后,似乎你正确使用了Type。我认为你的问题在于使用了T。你必须记住,在Java中存在类型擦除。这意味着在运行时,所有T的实例都被替换为Object。因此,在运行时,你传递给GSON的实际上是MyJson<Object>。如果你尝试使用代替<T>的具体类进行此操作,我相信它会起作用。

Google Gson - 反序列化list<class>对象?(泛型类型)


好的,我的解决方案是在创建JsonDownloader类时将Type作为参数发送,然后一切都可以正常工作。我之前不知道类型擦除,所以感谢您指出这一点。 - VizGhar

7

对我来说,上面的答案没有起作用,在试错之后,我的代码是这样的:

public class AbstractListResponse<T> {
    private List<T> result;

    public List<T> getResult() {
        return this.result;
    }
}

重要的部分在这里是方法签名,包括左侧的“< T >”。
protected <T> AbstractListResponse<T> parseAbstractResponse(String json, TypeToken type) {
    return new GsonBuilder()
            .create()
            .fromJson(json, type.getType());
}

在调用Gson时,该方法接收泛型对象的TypeToken。
TypeToken<AbstractListResponse<MyDTO>> typeToken = new TypeToken<AbstractListResponse<MyDTO>>() {};
AbstractListResponse<MyDTO> responseBase = parseAbstractResponse(json, typeToken);

最后,TypeToken 可以使用 MyDTO,甚至可以使用一个简单的对象,只需要 MyDTO。

优秀的例子。完美解决了我的问题。 - Tomas Bisciak

3

如果您像我一样在学习Kotlin时遇到了困难,我发现这种方法很有效

val type = object : TypeToken<MyJson<MyObject>>() { }.type
val response = gson.fromJson<MyJson<MyObject>>(reader, type)

请注意,在调用泛型函数时,需要在函数名称后面的调用位置提供类型参数(参见此处)。

2
如果您正在使用gson 2.8.0或更高版本,则可以使用以下方法TypeToken#getParametized((Type rawType, Type... typeArguments))。
示例:
protected MyJson<T> doInBackground(Class<T> type, String json, Void... params) {

    GsonBuilder gson = new GsonBuilder();
    Type collectionType = TypeToken.getParameterized(MyJson.class, type).getType();

    MyJson<T> myJson = gson.create().fromJson(json, collectionType);

    System.out.println(myJson.getPosts()); // [true, false]
    return myJson;
}

我相信这在你的情况下会起作用。
感谢 此答案

1
我用上面的答案来找到了一种更通用的 Kotlin 方法(但你可以在 Java 中进行细微的调整后重用) 我有一个加载 Table<T>BaseDB<T>,而 Table 有一个 List<T>
fun parse(jsonString: String): Table<T> {
    //this gets the class for the type T
    val classT: Class<*> = (javaClass
        .genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<*>
    val type = TypeToken.getParameterized(Table::class.java, classT).type
    return GsonHelper.gson.fromJson<Table<T>>(jsonString, type)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接