【问题标题】:Deserializing Generic Types with GSON使用 GSON 反序列化泛型类型
【发布时间】:2013-08-26 04:20:52
【问题描述】:

在我的 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 是正确的。

那么有没有什么方法可以在不创建自定义反序列化器的情况下实现反序列化调用呢?

【问题讨论】:

  • When I use MyJson instead of MyJson 是什么意思
  • 嘿,我的意思是 MyJson 而不是 T 对不起
  • 我很想知道你为什么使用GsonBuilder 来获取Gson 实例,而不是直接使用new Gson.fromJson。从这个建设者那里可以获得什么样的好处?
  • @SebasSBM GsonBuilder 有一个 registerTypeAdapter 方法是实现类型的自定义反序列化器所必需的。

标签: java android json deserialization gson


【解决方案1】:

你必须在反序列化的时候指定T的类型。如果Gson 不知道要实例化什么Type,您的Listposts 将如何创建?它不能永远停留在T。因此,您将提供类型 T 作为 Class 参数。

现在假设posts 的类型是String,您可以将MyJson&lt;String&gt; 反序列化为(为简单起见,我还添加了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;
}

同样,反序列化 MyJsonBoolean 对象

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&lt;T&gt; 我的例子是

public class MyJson<T> {

    public List<T> posts;

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

因此,如果您要反序列化 List&lt;MyObject&gt;,您可以调用该方法

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

【讨论】:

  • 您的代码中究竟在哪里使用了Class&lt;T&gt; type
  • @Mr.Yetti 参数type 不直接使用,但仍指定&lt;T&gt; 在方法代码中其他任何地方的类型。因此,当您将type 作为MyObject.class 传递时,该方法与TypeToken&lt;MyJson&lt;MyObject&gt;&gt; 一起使用并返回MyJson&lt;MyObject&gt;
  • 不,它不会因为运行时的类型擦除。因此,无论您作为参数传递什么类型,您的函数都将始终返回 MyJson&lt;Object&gt;
  • @Mr.Yetti 类型擦除是运行时的事情,与type 参数无关,该参数存在于此处以满足MyJson&lt;MyObject&gt; myJson = doInBackground(...) 等语句中方法的通用返回类型在编译时。去掉type,看看抛出了什么编译错误就明白我在说什么了。
  • 你得到了什么确切的错误? doInBackground 函数必须在泛型类中,例如class JsonDownloader&lt;T&gt;,否则你的代码无论如何都不会编译。因此,type 参数无关紧要。如果该方法不在泛型类中,则签名 &lt;T&gt; MyJson&lt;T&gt; doInBackground(String json, Void... params) 足以避免显式强制转换,但这仍然无法避免运行时错误。
【解决方案2】:

你试过了吗?

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

编辑

阅读this 帖子后,您似乎使用Type 是正确的。我相信您的问题是使用T。您必须记住,Java 有类型擦除。这意味着在运行时T 的所有实例都将替换为Object。因此,在运行时您传递的 GSON 确实是 MyJson&lt;Object&gt;。如果你用一个具体的类来代替&lt;T&gt;,我相信它会起作用。

Google Gson - deserialize list<class> object? (generic type)

【讨论】:

  • 好的,我的解决方案是在创建 JsonDownloader 类时将 Type 作为参数发送,然后一切正常。我不知道 Type Erasure,所以感谢您指点我。
【解决方案3】:

所以上面的答案对我不起作用,经过反复试验,我的代码就是这样结束的:

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

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

这里重要的部分是方法签名,包括左边的''。

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。

【讨论】:

  • 很好的例子。完美解决我的问题。
【解决方案4】:

对于像我一样在 Kotlin 中苦苦挣扎的人,我已经找到了这种工作方式

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

请注意,调用泛型函数需要在函数名称之后的调用站点上的类型参数(参见here

【讨论】:

    【解决方案5】:

    如果您使用的是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;
    }
    

    我相信这对你的情况有效。

    感谢this answer

    【讨论】:

      【解决方案6】:

      我使用上述答案在 Kotlin 中找出更通用的方法(但您可以在 java 中重用,只需稍作调整) 我有BaseDB&lt;T&gt;,它加载Table&lt;T&gt;,而Table 有一个List&lt;T&gt;

      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)
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-29
        相关资源
        最近更新 更多