Google Gson: ¿deserializar el objeto de lista <clase>? (tipo genérico)

441

Quiero transferir un objeto de lista a través de Google Gson, pero no sé cómo deserializar los tipos genéricos.

Lo que probé después de mirar esto (la respuesta de BalusC):

MyClass mc = new Gson().fromJson(result, new List<MyClass>(){}.getClass());

pero luego aparece un error en eclipse que dice "El tipo new List () {} debe implementar el método abstracto heredado ..." y si uso una solución rápida obtengo un monstruo de más de 20 stubs de método.

Estoy bastante seguro de que hay una solución más fácil, ¡pero parece que no puedo encontrarla!

Editar:

Ahora tengo

Type listType = new TypeToken<List<MyClass>>()
                {
                }.getType();

MyClass mc = new Gson().fromJson(result, listType);

Sin embargo, obtengo la siguiente excepción en la línea "fromJson":

java.lang.NullPointerException
at org.apache.harmony.luni.lang.reflect.ListOfTypes.length(ListOfTypes.java:47)
at org.apache.harmony.luni.lang.reflect.ImplForType.toString(ImplForType.java:83)
at java.lang.StringBuilder.append(StringBuilder.java:203)
at com.google.gson.JsonDeserializerExceptionWrapper.deserialize(JsonDeserializerExceptionWrapper.java:56)
at com.google.gson.JsonDeserializationVisitor.invokeCustomDeserializer(JsonDeserializationVisitor.java:88)
at com.google.gson.JsonDeserializationVisitor.visitUsingCustomHandler(JsonDeserializationVisitor.java:76)
at com.google.gson.ObjectNavigator.accept(ObjectNavigator.java:106)
at com.google.gson.JsonDeserializationContextDefault.fromJsonArray(JsonDeserializationContextDefault.java:64)
at com.google.gson.JsonDeserializationContextDefault.deserialize(JsonDeserializationContextDefault.java:49)
at com.google.gson.Gson.fromJson(Gson.java:568)
at com.google.gson.Gson.fromJson(Gson.java:515)
at com.google.gson.Gson.fromJson(Gson.java:484)
at com.google.gson.Gson.fromJson(Gson.java:434)

Yo hago JsonParseExceptions de captura y "número" no es nulo.

Verifiqué listType con el depurador y obtuve lo siguiente:

  • tipo de lista
    • args = ListOfTypes
      • lista = nulo
      • resolveTypes = Tipo [1]
    • cargador = PathClassLoader
    • ownerType0 = nulo
    • ownerTypeRes = nulo
    • rawType = Class (java.util.ArrayList)
    • rawTypeName = "java.util.ArrayList"

entonces parece que la invocación "getClass" no funcionó correctamente. Alguna sugerencia...?

Edit2: he comprobado en la Guía del usuario de Gson . Menciona una excepción de tiempo de ejecución que debería ocurrir durante el análisis de un tipo genérico a Json. Lo hice "mal" (no se muestra arriba), como en el ejemplo, pero no obtuve esa excepción en absoluto. Así que cambié la serialización como se sugiere en la guía del usuario. Sin embargo, no ayudó.

Edit3: Resuelto, mira mi respuesta a continuación.

Medusa
fuente
1
La respuesta que apuntaste, usa TokenType. ¿Has intentado de esa manera?
Nishant
Acabo de recibir la misma pista como respuesta. la próxima vez daré un vistazo más de cerca al ejemplo. ;)
medusa
¿Puedes probar una implementación de list in type token? Dado que su tipo sin formato es la lista de matriz, debe probar la lista de matriz.
uncaught_exceptions

Respuestas:

954

Método para deserializar la colección genérica:

import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;

...

Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> yourClassList = new Gson().fromJson(jsonArray, listType);

Dado que varias personas en los comentarios lo han mencionado, aquí hay una explicación de cómo TypeTokense usa la clase. La construcción new TypeToken<...>() {}.getType()captura un tipo de tiempo de compilación (entre el <y >) en un java.lang.reflect.Typeobjeto de tiempo de ejecución . A diferencia de un Classobjeto, que solo puede representar un tipo sin formato (borrado), el Typeobjeto puede representar cualquier tipo en el lenguaje Java, incluida una instanciación parametrizada de un tipo genérico.

La TypeTokenclase en sí no tiene un constructor público, porque no se supone que lo construyas directamente. En su lugar, siempre construye una subclase anónima (de ahí {}que sea una parte necesaria de esta expresión).

Debido a la eliminación de tipos, la TypeTokenclase solo puede capturar tipos que son completamente conocidos en tiempo de compilación. (Es decir, no se puede hacer new TypeToken<List<T>>() {}.getType()para un parámetro de tipo T).

Para obtener más información, consulte la documentación de la TypeTokenclase .

Excepciones no detectadas
fuente
31
En las nuevas versiones de GSON, el constructor TypeToken no es público, por lo tanto, aquí obtiene un error no visible del constructor. ¿Qué tienes que hacer en este caso?
Pablo
8
Usando la versión real de GSON (2.2.4) funciona de nuevo. Puedes acceder al constructor aquí.
mi objeto json comienza con un objeto, luego contiene una matriz del objeto que deseo { "myObjectArray":[ {....} , {....} , {....} ] }, he creado el archivo de modelo {....}, ¿cómo obtengo este código genérico de colección para no asumir que mi elemento raíz es una matriz sin crear un nuevo archivo de objeto anidado?
CQM
77
Se requieren las siguientes importaciones: import java.lang.reflect.Type; import com.google.gson.reflect.TypeToken
Umair Saleem
44
Esto es bueno si YourClass está arreglado en código. ¿Qué pasa si la clase viene en tiempo de ejecución?
jasxir
273

Otra forma es usar una matriz como tipo, por ejemplo:

MyClass[] mcArray = gson.fromJson(jsonString, MyClass[].class);

De esta manera, evita toda la molestia con el objeto Tipo, y si realmente necesita una lista, siempre puede convertir la matriz en una lista de la siguiente manera:

List<MyClass> mcList = Arrays.asList(mcArray);

En mi humilde opinión esto es mucho más legible.

Y para que sea una lista real (que se puede modificar, vea las limitaciones de Arrays.asList()), simplemente haga lo siguiente:

List<MyClass> mcList = new ArrayList<>(Arrays.asList(mcArray));
DevNG
fuente
44
¡esto es genial! ¿Cómo puedo usarlo con reflexión? ¡No sé el MyClassvalor y se definirá dinámicamente!
Amin Sh
1
nota: con esto, tenga cuidado de que mcList no sea una lista completa. Muchas cosas no funcionarán.
njzk2
44
¿Cómo usarlo con genéricos? T[] yourClassList = gson.fromJson(message, T[].class);// No se puede seleccionar a partir variable de tipo
Pawel Cioch
2
@MateusViccari en el momento de ese comentario, mcListen esta respuesta fue solo el resultado de la llamada a Arrays.asList. Este método devuelve una lista en la que la mayoría de los métodos opcionales, si no todos, se dejan sin implementar y arrojan excepciones. Por ejemplo, no puede agregar ningún elemento a esa lista. Como sugiere la edición posterior, Arrays.asListtiene limitaciones, y envolverlo en un archivo real le ArrayListpermite obtener una lista que es más útil en muchos casos.
njzk2
2
Si necesita construir un tipo de matriz en tiempo de ejecución para un tipo de elemento arbitrario, puede usar Array.newInstance(clazz, 0).getClass()como se describe en la respuesta de David Wood .
Daniel Pryden
31

Consulte esta publicación. Java Type Generic como argumento para GSON

Tengo una mejor solución para esto. Aquí está la clase de contenedor para la lista para que el contenedor pueda almacenar exactamente el tipo de lista.

public class ListOfJson<T> implements ParameterizedType
{
  private Class<?> wrapped;

  public ListOfJson(Class<T> wrapper)
  {
    this.wrapped = wrapper;
  }

  @Override
  public Type[] getActualTypeArguments()
  {
      return new Type[] { wrapped };
  }

  @Override
  public Type getRawType()
  {
    return List.class;
  }

  @Override
  public Type getOwnerType()
  {
    return null;
  }
}

Y luego, el código puede ser simple:

public static <T> List<T> toList(String json, Class<T> typeClass)
{
    return sGson.fromJson(json, new ListOfJson<T>(typeClass));
}
Más feliz
fuente
¿Qué es mEntity.rulePattern?
Al Lelopath
Es solo un objeto de muestra para prueba. No necesitas preocuparte por eso. Use el método toList y todo irá bien.
feliz el
@Happier Estoy intentando implementar este Gson 2.8.2 y parece que no funciona. Cualquier oportunidad stackoverflow.com/questions/50743932/… puede echar un vistazo y hacerme saber lo que me estoy perdiendo
Praveen
1
@Praveen Lo he intentado de esta manera en 2.8.2, funciona como original.
feliz
31

Desde entonces Gson 2.8, podemos crear funciones de utilidad como

public <T> List<T> getList(String jsonArray, Class<T> clazz) {
    Type typeOfT = TypeToken.getParameterized(List.class, clazz).getType();
    return new Gson().fromJson(jsonArray, typeOfT);
}

Ejemplo usando

String jsonArray = ...
List<User> user = getList(jsonArray, User.class);
Phan Van Linh
fuente
2
TypeToken#getParameterizedse ve mucho mejor que el truco con una subclase anónima
Nikolay Kulachenko
esta debería ser la respuesta aceptada; al menos para las versiones más recientes
ccpizza
2
Esta es la respuesta perfecta. Resuelve mi problema
donmj
Copié su método "tal cual" y no funciona: el compilador dice "El método getParameterized (Class <List>, Class <T>) no está definido para el tipo TypeToken". Verifiqué tanto mi versión de Gson (2.8.0) como la documentación y todo está bien en este lado ... Terminé usando la solución @Happier que funciona bien
Leguminator
@leguminator ¿importó TypeToken correctamente? y estás usando java o kotlin. Trataré de probar nuevamente
Phan Van Linh
26

Wep, otra forma de lograr el mismo resultado. Lo usamos por su legibilidad.

En lugar de hacer esta oración difícil de leer:

Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> list = new Gson().fromJson(jsonArray, listType);

Cree una clase vacía que extienda una Lista de su objeto:

public class YourClassList extends ArrayList<YourClass> {}

Y úsalo cuando analices el JSON:

List<YourClass> list = new Gson().fromJson(jsonArray, YourClassList.class);
Roc Boronat
fuente
9

Para Kotlin simplemente:

import java.lang.reflect.Type
import com.google.gson.reflect.TypeToken
...
val type = object : TypeToken<List<T>>() {}.type

o, aquí hay una función útil:

fun <T> typeOfList(): Type {
    return object : TypeToken<List<T>>() {}.type
}

Luego, para usar:

val type = typeOfList<YourMagicObject>()
Chad Bingham
fuente
He usado su código para crear esta función usando tipos reified: inline fun <reified T> buildType() = object : TypeToken<T>() {}.type!!y llamarlo con el tipo de Lista:buildType<List<YourMagicObject>>()
coffeemakr
@coffeemakr No necesita tipos reified aquí.
Chad Bingham el
Oh. Pero, ¿por qué crea el token de tipo de un ArrayList buildTypey también llama a la función con el tipo genérico? ¿Es esto un error tipográfico? - Esto crearía ArrayList <ArrayList <YourMagicObject>>
coffeemakr
@coffeemakr ah, sí. Error tipográfico
Chad Bingham el
7
public static final <T> List<T> getList(final Class<T[]> clazz, final String json)
{
    final T[] jsonToObject = new Gson().fromJson(json, clazz);

    return Arrays.asList(jsonToObject);
}

Ejemplo:

getList(MyClass[].class, "[{...}]");
kayz1
fuente
Bueno Pero esto duplica DevNGla respuesta anterior, escrita 2 años antes: stackoverflow.com/a/17300003/1339923 (y lea esa respuesta para advertencias sobre este enfoque)
Lambart
6

Como responde a mi pregunta original, acepté la respuesta de doc_180, pero si alguien se encuentra con este problema nuevamente, también responderé la segunda mitad de mi pregunta:

El NullPointerError que describí no tenía nada que ver con la Lista en sí, ¡sino con su contenido!

La clase "MyClass" no tenía un constructor "sin argumentos", y ninguno tenía su superclase. Una vez que agregué un simple constructor "MyClass ()" a MyClass y su superclase, todo funcionó bien, incluida la serialización y deserialización de la lista como lo sugiere el documento doc_180.

Medusa
fuente
1
Si tiene una lista de clases abstractas, obtendrá el mismo error. Supongo que este es el mensaje de error general de GSON para "No se puede crear una instancia de clase".
Dibujó el
El consejo sobre agregar un constructor me ayudó a darme cuenta de por qué tenía todos los valores nulos. Tenía nombres de campo como "To" y "From" en mi cadena JSON, pero los campos correspondientes en mi objeto eran "to" y "from" en minúsculas, por lo que se omitieron
Runa
4

Aquí hay una solución que funciona con un tipo definido dinámicamente. El truco es crear el tipo adecuado de matriz usando Array.newInstance ().

    public static <T> List<T> fromJsonList(String json, Class<T> clazz) {
    Object [] array = (Object[])java.lang.reflect.Array.newInstance(clazz, 0);
    array = gson.fromJson(json, array.getClass());
    List<T> list = new ArrayList<T>();
    for (int i=0 ; i<array.length ; i++)
        list.add(clazz.cast(array[i]));
    return list; 
}
David Wood
fuente
Esta respuesta sería mejor si solía class.cast()evitar la advertencia no verificada causada por enviar a (T). O, mejor aún, no se moleste con la conversión y use algo como Arrays.asList()convertir de una matriz a una List<T>. Además, no es necesario pasar una longitud a Array.newInstance(): una matriz de tamaño cero será lo suficientemente buena como para llamar getClass().
Daniel Pryden
Gracias por la sugerencia, hice los cambios sugeridos y actualicé la publicación anterior.
David Wood
2

Quiero agregar para una posibilidad más. Si no desea utilizar TypeToken y desea convertir la matriz de objetos json en una ArrayList, puede proceder así:

Si su estructura json es como:

{

"results": [
    {
        "a": 100,
        "b": "value1",
        "c": true
    },
    {
        "a": 200,
        "b": "value2",
        "c": false
    },
    {
        "a": 300,
        "b": "value3",
        "c": true
    }
]

}

y tu estructura de clase es como:

public class ClassName implements Parcelable {

    public ArrayList<InnerClassName> results = new ArrayList<InnerClassName>();
    public static class InnerClassName {
        int a;
        String b;
        boolean c;      
    }
}

entonces puedes analizarlo como:

Gson gson = new Gson();
final ClassName className = gson.fromJson(data, ClassName.class);
int currentTotal = className.results.size();

Ahora puede acceder a cada elemento del objeto className.

Apurva Sharma
fuente
1

Consulte el ejemplo 2 para la comprensión de la clase 'Tipo' de Gson.

Ejemplo 1: en este deserilizeResturant utilizamos la matriz Employee [] y obtenemos los detalles

public static void deserializeResturant(){

       String empList ="[{\"name\":\"Ram\",\"empId\":1},{\"name\":\"Surya\",\"empId\":2},{\"name\":\"Prasants\",\"empId\":3}]";
       Gson gson = new Gson();
       Employee[] emp = gson.fromJson(empList, Employee[].class);
       int numberOfElementInJson = emp.length();
       System.out.println("Total JSON Elements" + numberOfElementInJson);
       for(Employee e: emp){
           System.out.println(e.getName());
           System.out.println(e.getEmpId());
       }
   }

Ejemplo 2

//Above deserilizeResturant used Employee[] array but what if we need to use List<Employee>
public static void deserializeResturantUsingList(){

    String empList ="[{\"name\":\"Ram\",\"empId\":1},{\"name\":\"Surya\",\"empId\":2},{\"name\":\"Prasants\",\"empId\":3}]";
    Gson gson = new Gson();

    // Additionally we need to se the Type then only it accepts List<Employee> which we sent here empTypeList
    Type empTypeList = new TypeToken<ArrayList<Employee>>(){}.getType();


    List<Employee> emp = gson.fromJson(empList, empTypeList);
    int numberOfElementInJson = emp.size();
    System.out.println("Total JSON Elements" + numberOfElementInJson);
    for(Employee e: emp){
        System.out.println(e.getName());
        System.out.println(e.getEmpId());
    }
}
Ram Patro
fuente
0

Me gustó la respuesta de kays1 pero no pude implementarla. Así que construí mi propia versión usando su concepto.

public class JsonListHelper{
    public static final <T> List<T> getList(String json) throws Exception {
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
        Type typeOfList = new TypeToken<List<T>>(){}.getType();
        return gson.fromJson(json, typeOfList);
    }
}

Uso:

List<MyClass> MyList= JsonListHelper.getList(jsonArrayString);
mike83_dev
fuente
Seguramente esto no puede funcionar ya que estás tratando de usar T en tiempo de compilación. Esto deserializará efectivamente a una Lista de StringMap, ¿no?
JHH
0

En mi caso, la respuesta de @ uncaught_exceptions no funcionó, tuve que usar en List.classlugar de java.lang.reflect.Type:

String jsonDuplicatedItems = request.getSession().getAttribute("jsonDuplicatedItems").toString();
List<Map.Entry<Product, Integer>> entries = gson.fromJson(jsonDuplicatedItems, List.class);
Andrei
fuente