Google Gson: deserializzare l'elenco <class> oggetto? (tipo generico)


441

Voglio trasferire un oggetto elenco tramite Google Gson, ma non so come deserializzare i tipi generici.

Cosa ho provato dopo aver visto questo (la risposta di BalusC):

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

ma poi ricevo un errore in eclipse che dice "Il tipo new List () {} deve implementare il metodo astratto ereditato ..." e se uso una soluzione rapida ottengo un mostro di oltre 20 matrici di metodi.

Sono abbastanza sicuro che esiste una soluzione più semplice, ma non riesco a trovarla!

Modificare:

Adesso ho

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

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

Tuttavia, ottengo la seguente eccezione nella riga "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)

Io faccio JsonParseExceptions cattura e "risultato" non è nullo.

Ho controllato listType con il debugger e ho ottenuto quanto segue:

  • tipo di elenco
    • args = ListOfTypes
      • list = null
      • resolvedTypes = Tipo [1]
    • loader = PathClassLoader
    • ownerType0 = null
    • ownerTypeRes = null
    • rawType = Class (java.util.ArrayList)
    • rawTypeName = "java.util.ArrayList"

quindi sembra che l'invocazione "getClass" non abbia funzionato correttamente. Eventuali suggerimenti...?

Edit2: ho controllato nella Guida dell'utente di Gson . Indica un'eccezione di runtime che dovrebbe verificarsi durante l'analisi di un tipo generico su Json. L'ho fatto "sbagliato" (non mostrato sopra), proprio come nell'esempio, ma non ho ottenuto alcuna eccezione. Quindi ho modificato la serializzazione come suggerito nella guida per l'utente. Non ha aiutato, però.

Edit3: risolto, vedi la mia risposta di seguito.


1
La risposta che hai indicato, utilizza TokenType. Hai provato in quel modo?
Nishant,

ho appena ricevuto lo stesso suggerimento di una risposta. la prossima volta darò l'esempio più da vicino. ;)
meduse

Puoi provare un'implementazione dell'elenco nel tipo token? Poiché il tipo non elaborato è l'elenco di array, dovresti provare l'elenco di array.
uncaught_exceptions,

Risposte:


954

Metodo per deserializzare la raccolta generica:

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

Poiché diverse persone nei commenti lo hanno menzionato, ecco una spiegazione di come TypeTokenviene utilizzata la classe. La costruzione new TypeToken<...>() {}.getType()acquisisce un tipo di tempo di compilazione (tra <e >) in un java.lang.reflect.Typeoggetto runtime . A differenza di un Classoggetto, che può rappresentare solo un tipo grezzo (cancellato), l' Typeoggetto può rappresentare qualsiasi tipo nel linguaggio Java, inclusa un'istanza parametrizzata di un tipo generico.

La TypeTokenclasse stessa non ha un costruttore pubblico, perché non dovresti costruirla direttamente. Invece, costruisci sempre una sottoclasse anonima (da cui il {}, che è una parte necessaria di questa espressione).

A causa della cancellazione dei tipi, la TypeTokenclasse è in grado di catturare solo i tipi che sono completamente noti al momento della compilazione. (Cioè, non puoi fare new TypeToken<List<T>>() {}.getType()per un parametro di tipo T.)

Per ulteriori informazioni, consultare la documentazione per la TypeTokenclasse .


31
Nelle nuove versioni di GSON il contrettore TypeToken non è pubblico, quindi qui si ottiene l'errore del costruttore non visibile. Cosa devi fare in questo caso?
Pablo,

8
Utilizzando la versione attuale di GSON (2.2.4) funziona di nuovo. Puoi accedere al costruttore qui.

il mio oggetto json inizia con un oggetto, quindi contiene una matrice dell'oggetto che desidero { "myObjectArray":[ {....} , {....} , {....} ] }, ho creato il file modello per {....}, come posso ottenere questo codice di raccolta generico per non presumere che il mio elemento radice sia un array senza creare un nuovo file oggetto nidificato
CQM,

7
A seguito di importazioni richieste --- import java.lang.reflect.Type; import com.google.gson.reflect.TypeToken
Umair Saleem

4
Questo è utile se YourClass è corretto nel codice. Cosa succede se la classe arriva in fase di esecuzione?
jasxir,

273

Un altro modo è utilizzare un array come tipo, ad esempio:

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

In questo modo eviti tutte le seccature con l'oggetto Type e se hai davvero bisogno di un elenco puoi sempre convertire l'array in un elenco:

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

IMHO questo è molto più leggibile.

E per renderlo un elenco effettivo (che può essere modificato, vedi i limiti di Arrays.asList()), procedi come segue:

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

4
è fantastico! Come posso usarlo con la riflessione? Non conosco il MyClassvalore e verrà definito in modo dinamico!
Amin Sh,

1
nota: con questo, fai attenzione che mcList non sia un elenco completo. molte cose non funzioneranno.
njzk2,

4
Come usarlo con i generici? T[] yourClassList = gson.fromJson(message, T[].class);// non può selezionare dalla variabile di tipo
Pawel Cioch il

2
@MateusViccari al momento di quel commento, mcListin questa risposta era solo il risultato della chiamata a Arrays.asList. Questo metodo restituisce un elenco in cui la maggior parte, se non tutti, i metodi opzionali non vengono implementati e generano eccezioni. Ad esempio, non è possibile aggiungere alcun elemento a tale elenco. Come suggerisce la modifica successiva, Arrays.asListpresenta dei limiti e il suo inserimento in un reale ArrayListconsente di ottenere un elenco che è più utile in molti casi.
njzk2

2
Se è necessario costruire un tipo di array in fase di esecuzione per un tipo di elemento arbitrario, è possibile utilizzare Array.newInstance(clazz, 0).getClass()come descritto nella risposta di David Wood .
Daniel Pryden,

31

Fare riferimento a questo post. Tipo Java generico come argomento per GSON

Ho una soluzione migliore per questo. Ecco la classe wrapper per l'elenco in modo che il wrapper possa memorizzare esattamente il tipo di elenco.

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

E quindi, il codice può essere semplice:

public static <T> List<T> toList(String json, Class<T> typeClass)
{
    return sGson.fromJson(json, new ListOfJson<T>(typeClass));
}

Che cosa è mEntity.rulePattern?
Al Lelopath,

È solo un oggetto campione per il test. Non devi preoccupartene. Usa il metodo toList e tutto va bene.
Happier

@Happier Sto cercando di implementare questo Gson 2.8.2 e sembra non funzionare. Qualunque possibilità stackoverflow.com/questions/50743932/… puoi dare un'occhiata e farmi sapere cosa mi sto perdendo
Praveen,

1
@Praveen Ho provato in questo modo in 2.8.2, funziona come originale.
Happier,

31

Da allora Gson 2.8, possiamo creare funzioni util come

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

Esempio usando

String jsonArray = ...
List<User> user = getList(jsonArray, User.class);

2
TypeToken#getParameterizedsembra un modo migliore dell'hacking con una sottoclasse anonima
Nikolay Kulachenko,

questa dovrebbe essere la risposta accettata; almeno per le versioni più recenti
ccpizza

2
Questa è la risposta perfetta Risolve il mio problema
donmj,

Ho copiato il metodo "così com'è" e non funziona: il compilatore dice "Il metodo getParameterized (Class <List>, Class <T>) non è definito per il tipo TypeToken". Ho controllato sia la mia versione Gson (2.8.0) sia la documentazione e tutto è andato bene da questo lato ... Ho finito per usare la soluzione @Happier che funziona benissimo
leguminator

@leguminator hai importato TypeToken corretto? e stai usando java o kotlin. Proverò di nuovo a provare
Phan Van Linh,

26

Wep, un altro modo per ottenere lo stesso risultato. Lo usiamo per la sua leggibilità.

Invece di fare questa frase difficile da leggere:

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

Crea una classe vuota che estende un elenco del tuo oggetto:

public class YourClassList extends ArrayList<YourClass> {}

E usalo quando analizzi JSON:

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

9

Per Kotlin semplicemente:

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

oppure, ecco una funzione utile:

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

Quindi, per usare:

val type = typeOfList<YourMagicObject>()

Ho usato il tuo codice per creare questa funzione usando tipi reificati: inline fun <reified T> buildType() = object : TypeToken<T>() {}.type!!e chiamalo con il tipo Elenco:buildType<List<YourMagicObject>>()
coffeemakr

@coffeemakr Non hai bisogno di tipi reificati qui.
Chad Bingham,

Oh. Ma perché crei il token di tipo di un ArrayList buildTypee chiami anche la funzione con il tipo generico? È un errore di battitura? - Ciò creerebbe ArrayList <ArrayList <YourMagicObject>>
coffeemakr

@coffeemakr ah, yeah. Errore di battitura
Chad Bingham,

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

Esempio:

getList(MyClass[].class, "[{...}]");

Buona Ma questa duplica DevNGla risposta sopra, scritta 2 anni prima: stackoverflow.com/a/17300003/1339923 (e leggi quella risposta per avvertenze su questo approccio)
Lambart,

6

Poiché risponde alla mia domanda originale, ho accettato la risposta di doc_180, ma se qualcuno si imbatte nuovamente in questo problema, risponderò anche alla seconda metà della mia domanda:

Il NullPointerError che ho descritto non aveva nulla a che fare con l'Elenco stesso, ma con il suo contenuto!

La classe "MyClass" non aveva un costruttore "no args", e nemmeno la sua superclasse. Una volta aggiunto un semplice costruttore "MyClass ()" a MyClass e alla sua superclasse, tutto ha funzionato bene, inclusa la serializzazione e la deserializzazione dell'elenco come suggerito da doc_180.


1
Se hai un elenco di classi astratte otterrai lo stesso errore. Immagino che questo sia il messaggio di errore generale di GSON per "Impossibile istanziare la classe".
Ha

Il consiglio sull'aggiunta di un costruttore mi ha aiutato a capire perché avevo tutti i valori null. Avevo nomi di campo come "A" e "Da" nella mia stringa JSON, ma i campi corrispondenti nel mio oggetto erano "a" e "da" in minuscolo, quindi sono stati saltati
Rune

4

Ecco una soluzione che funziona con un tipo definito dinamicamente. Il trucco sta nel creare il giusto tipo di array 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; 
}

Questa risposta sarebbe migliore se si class.cast()evitasse l'avvertimento non controllato causato dal cast (T). O, ancora meglio, non preoccuparti del casting e usa qualcosa di simile Arrays.asList()per convertire da un array a List<T>. Inoltre, non è necessario passare una lunghezza a Array.newInstance(): un array di dimensioni zero sarà abbastanza buono da richiamare getClass().
Daniel Pryden,

Grazie per il suggerimento, ho apportato le modifiche suggerite e ho aggiornato il post sopra.
David Wood,

2

Voglio aggiungere un'altra possibilità. Se non si desidera utilizzare TypeToken e si desidera convertire l'array json objects in un ArrayList, è possibile procedere in questo modo:

Se la tua struttura json è simile:

{

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

}

e la struttura della tua classe è come:

public class ClassName implements Parcelable {

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

quindi puoi analizzarlo come:

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

Ora puoi accedere a ciascun elemento dell'oggetto className.


1

Fare riferimento all'esempio 2 per la comprensione della classe "Tipo" di Gson.

Esempio 1: In questo deserilizeResturant abbiamo usato l'array Employee [] per ottenere i dettagli

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

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

0

Mi è piaciuta la risposta di kays1 ma non sono riuscita a implementarla. Quindi ho costruito la mia versione usando il suo concetto.

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

Sicuramente questo non può funzionare poiché stai cercando di usare T in fase di compilazione. Questo si deserializzerà effettivamente in un elenco di StringMap, no?
JHH,

0

Nel mio caso la risposta di @ uncaught_exceptions non ha funzionato, ho dovuto usare List.classinvece di java.lang.reflect.Type:

String jsonDuplicatedItems = request.getSession().getAttribute("jsonDuplicatedItems").toString();
List<Map.Entry<Product, Integer>> entries = gson.fromJson(jsonDuplicatedItems, List.class);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.