【问题标题】:How can I use Google GSON to deserialize a JSON array into a a Collection of a generic type? [duplicate]如何使用 Google GSON 将 JSON 数组反序列化为泛型类型的 Collection? [复制]
【发布时间】:2012-05-08 12:38:42
【问题描述】:

我正在编写一些用于从网站检索资源的代码。它看起来像这样:

public Collection<Project> getProjects() {
        String json = getJsonData(methods.get(Project.class)); //Gets a json list, ie [1, 2, 3, 4]
        Gson gson = new Gson();
        Type collectionType = new TypeToken<Collection<Project>>() {}.getType();
        return gson.fromJson(json, collectionType);
    }

所以我很自然地尝试使用 Java 泛型对其进行抽象。

/*
     * Deserialize a json list and return a collection of the given type.
     * 
     * Example usage: getData(AccountQuota.class) -> Collection<AccountQuota>
     */
    @SuppressWarnings("unchecked")
    public <T> Collection<T> getData(Class<T> cls) {
        String json = getJsonData(methods.get(cls)); //Gets a json list, ie [1, 2, 3, 4]
        Gson gson = new Gson();
        Type collectionType = new TypeToken<Collection<T>>(){}.getType();
        return (Collection<T>) gson.fromJson(json, collectionType);
}

不过,通用版本的代码不太好用。

public void testGetItemFromGetData() throws UserLoginError, ServerLoginError {
            Map<String,String> userData = GobblerAuthenticator.authenticate("foo@example.com", "mypassword");
            String client_key = userData.get("client_key");
            GobblerClient gobblerClient = new GobblerClient(client_key);
            ArrayList<Project> machines = new ArrayList<Project>();
            machines.addAll(gobblerClient.getData(Project.class));
            assertTrue(machines.get(0).getClass() == Project.class);
            Log.i("Machine", gobblerClient.getData(Project.class).toString());
        }


java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.gobbler.synchronization.Machine
at com.gobblertest.GobblerClientTest.testGetItemFromGetData(GobblerClientTest.java:53)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:545)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1551)

有问题的班级:

import java.util.Map;


public class Project {
    private int total_bytes_stored;
    private String name;
    private String user_data_guid;
    private int seqnum;
    private String guid;
    private Map current_checkpoint;
    private Map<String, String> upload_folder;
    // TODO: schema_version
    private boolean deleted;
    // TODO: download_folders

    public Project() {} // No args constructor used for GSON
}

我对 Java 泛型或 GSON 内部的所有细节都不是很熟悉,而且我的搜索也没有提供特别丰富的信息。这里有很多关于 SO 的问题,但大多数都指的是实现方法,比如我原来的方法。而GSON docs 似乎并没有涵盖这种特殊情况。再说一遍,如何使用 Google GSON 将 JSON 数组反序列化为泛型类型的 Collection?

【问题讨论】:

    标签: java generics gson


    【解决方案1】:

    我能想到的 Java 中最通用的类​​型是 Map&lt;String, Object&gt; 表示 JSON 对象,List&lt;Map&lt;String, Object&gt;&gt; 表示 JSON 对象数组。

    使用 GSON 库(我在撰写本文时使用的是 2.2.4 版)解析这个非常简单:

    import com.google.gson.Gson;
    
    import java.util.List;
    import java.util.Map;
    
    public class Test {
        public static void main(String[] args) {
            String json = "{\"name\":\"a name\"}";
            Gson GSON = new Gson();
            Map<String, Object> parsed = GSON.fromJson(json, Map.class);
            System.out.println(parsed.get("name"));
    
            String jsonList = "[{\"name\":\"a name\"}, {\"name\":\"a name2\"}]";
            List<Map<String, Object>> parsedList = GSON.fromJson(jsonList, List.class);
            for (Map<String, Object> parsedItem : parsedList) {
                System.out.println(parsedItem.get("name"));
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      我使用JsonReader反序列化json字符串如下。

      public class JSONReader<T> {
      
          .....
      
          private Class<T> persistentClass;
      
          public Class<T> getPersistentClass() {
              if (persistentClass == null) {
                  this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
              }
              return persistentClass;
          }
      
          public List<T> read(Reader reader) throws IOException {
              JsonReader jsonReader = new JsonReader(reader);
              List<T> objs = new ArrayList<T>();
              jsonReader.beginArray();      
              while (jsonReader.hasNext()) {
                  T obj = (new Gson()).fromJson(jsonReader, getPersistentClass());
                  if (logger.isFine()) {
                      logger.fine(obj.toString());
                  }
                  objs.add(obj);
              }
              jsonReader.endArray();
              jsonReader.close();
              return objs;
          }
      
          .....
      }
      

      您可以使用以下语句调用上述方法(将Json字符串转换为StringReader,并将StringReader对象传递给方法):

      public class Test extends JSONReader<MyClass> {
      
          .....
      
          public List<MyClass> parse(String jsonString) throws IOException {
              StringReader strReader = new StringReader(jsonString);
              List<MyClass> objs = read(strReader);
              return objs;
          }
      
          .....
      }
      

      希望可以工作!

      【讨论】:

      • 非常有用的替代品!
      【解决方案3】:

      我想你可以! From the Gson user guide:

      您可以通过为泛型类型指定正确的参数化类型来解决此问题。您可以使用 TypeToken 类来做到这一点。

      这意味着理论上你可以像这样包装你的地图:

      Type t = new TypeToken<Map<String,Machine>>() {}.getType();
      Map<String,Machine> map = (Map<String,Machine>) new Gson().fromJson("json", t);
      

      或者(在我的情况下)我必须像这样包装一个列表:

      Type t = new TypeToken<List<SearchResult>>() {}.getType();
      List<SearchResult> list = (List<SearchResult>) new Gson().fromJson("json", t);
      
      for (SearchResult r : list) {
        System.out.println(r);
      }
      

      (我遇到了异常“java.lang.ClassCastException: com.google.gson.internal.StringMap cannot be cast to my.SearchResult”。)

      【讨论】:

      • 是的,它对我来说很好用。但是在使用上述代码的 Proguard 进行混淆时,得到“未找到匿名类(List)”。我错过了什么吗?否则必须更改或添加代码以将匿名类保留在 proguard 配置文件中。?
      【解决方案4】:

      很遗憾,你不能。泛型类型已被编译器擦除,Gson 无法知道您拥有哪些类型或它们的字段是什么。

      【讨论】:

      • 很高兴听到,如果只是为了结束。我知道这个话题可能有点涉及,但你能简要解释一下原因吗?
      • 为了向后兼容和实现简单,JVM 在运行时不知道类型参数背后的类型。通常你不在乎,但这是你关心的情况之一。网上有很多关于“Java 擦除”的文章,其中有更多的细节。
      • 其实可以,查看@jevon stackoverflow.com/a/12576189/608805的答案
      猜你喜欢
      • 1970-01-01
      • 2013-08-26
      • 1970-01-01
      • 1970-01-01
      • 2017-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多