2012-04-27 17 views
10

Estoy escribiendo un código que se utilizará para recuperar recursos de un sitio web. Todo tipo de ve así:¿Cómo puedo usar Google GSON para deserializar una matriz JSON en una Colección de tipo genérico?

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

Así que, naturalmente traté abstrayéndolo usando genéricos de 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); 
} 

Sin embargo, la versión genérica del código no funciona del todo.

public void testGetItemFromGetData() throws UserLoginError, ServerLoginError { 
      Map<String,String> userData = GobblerAuthenticator.authenticate("[email protected]", "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) 

la clase en cuestión:

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 
} 

No estoy muy familiarizado con todos los detalles de los genéricos de Java y los componentes internos GSON y mi búsqueda no ha sido particularmente informativo. Aquí hay un montón de preguntas sobre SO, pero la mayoría se refieren a métodos de implementación como el original que tuve. Y el GSON docs no parece cubrir este caso particular. Entonces, de nuevo, ¿cómo puedo usar Google GSON para deserializar una matriz JSON en una Colección de tipo genérico?

Respuesta

8

Lamentablemente, no se puede. El compilador ha borrado el tipo genérico y no hay forma de que Gson sepa qué tipos tiene o cuáles son sus campos.

+0

bueno para escuchar, si sólo para dar cierre. Entiendo que el tema probablemente esté un poco involucrado, pero ¿puedes explicar por qué brevemente? –

+1

Para compatibilidad con versiones anteriores y simplicidad de implementación, la JVM no conoce los tipos detrás de los parámetros de tipo en el tiempo de ejecución. Por lo general, no te importa, pero esta es una de las situaciones en las que lo haces. La web tiene muchos artículos sobre el "borrado de Java" que tienen más detalles. –

+0

En realidad, puede, consulte la respuesta dada por @jevon http://stackoverflow.com/a/12576189/608805 – raukodraug

32

¡Creo que puedes! From the Gson user guide:

Puede resolver este problema especificando el tipo parametrizado correcto para su tipo genérico. Puedes hacer esto usando la clase TypeToken.

Esto significa que usted podría, en teoría, envuelva su mapa de este modo:

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

O (en mi caso) que tenía que envolver un listado de este modo:

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

(Obtuve la excepción "java.lang.ClassCastException: com.google.gson.internal.StringMap no se puede convertir en my.SearchResult".)

+0

Sí, funciona bien para mí. Pero en la ofuscación usando Proguard del código anterior, obteniendo "clase anónima (Lista ) no encontrada". ¿Me estoy perdiendo algo? De lo contrario, debe cambiar o agregar código para mantener la clase anónima en el archivo de configuración de proguard. – Karthick

5

Utilicé JsonReader para deserializar json string de la siguiente manera.

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

    ..... 
} 

Puede llamar anteriormente el uso del método de abajo declaraciones (convertir cadena JSON a StringReader, y pasar StringReader objeto en el método):

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

    ..... 
} 

se espera que puedan obras!

+0

¡Alternativa muy útil! –

1

El tipo más genérico que se me ocurre en Java es un Map<String, Object> para representar un objeto JSON y List<Map<String, Object>> para representar una matriz de objetos JSON.

Es muy directa a analizar esta usando la biblioteca GSON (he usado la versión 2.2.4 en el momento de la escritura):

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")); 
     } 
    } 
} 
Cuestiones relacionadas