Google Gson - Listenobjekt <Klasse> deserialisieren? (generischer Typ)


441

Ich möchte ein Listenobjekt über Google Gson übertragen, weiß jedoch nicht, wie generische Typen deserialisiert werden sollen.

Was ich versucht , nachdem bei der Suche diese (BalusC Antwort):

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

Aber dann erhalte ich in Eclipse die Fehlermeldung "Der Typ new List () {} muss die geerbte abstrakte Methode implementieren ..." und wenn ich eine schnelle Lösung verwende, erhalte ich ein Monster mit über 20 Methodenstubs.

Ich bin mir ziemlich sicher, dass es eine einfachere Lösung gibt, aber ich scheine sie nicht zu finden!

Bearbeiten:

Jetzt habe ich

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

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

In der Zeile "fromJson" wird jedoch die folgende Ausnahme angezeigt:

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)

Ich tun fangen JsonParseExceptions und „Ergebnis“ ist nicht null.

Ich habe listType mit dem Debugger überprüft und Folgendes erhalten:

  • Listentyp
    • args = ListOfTypes
      • list = null
      • ResolutionTypes = Typ [1]
    • loader = PathClassLoader
    • ownerType0 = null
    • ownerTypeRes = null
    • rawType = Klasse (java.util.ArrayList)
    • rawTypeName = "java.util.ArrayList"

Es scheint also, dass der Aufruf von "getClass" nicht richtig funktioniert hat. Irgendwelche Vorschläge...?

Edit2: Ich habe im Gson-Benutzerhandbuch nachgesehen . Es wird eine Laufzeitausnahme erwähnt, die beim Parsen eines generischen Typs für Json auftreten sollte. Ich habe es "falsch" gemacht (oben nicht gezeigt), genau wie im Beispiel, aber diese Ausnahme überhaupt nicht bekommen. Also habe ich die Serialisierung wie im Benutzerhandbuch vorgeschlagen geändert. Hat aber nicht geholfen.

Edit3: Gelöst, siehe meine Antwort unten.


1
Die Antwort, auf die Sie hingewiesen haben, verwendet TokenType. Hast du es so versucht?
Nishant

habe gerade den gleichen Hinweis als Antwort bekommen. Das nächste Mal werde ich das Beispiel genauer betrachten. ;)
Quallen

Können Sie eine Implementierung der Liste im Typ Token versuchen? Da Ihr Rohtyp eine Array-Liste ist, sollten Sie die Array-Liste ausprobieren.
uncaught_exceptions

Antworten:


954

Methode zum Deserialisieren der generischen Sammlung:

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

Da es in den Kommentaren von mehreren Personen erwähnt wurde, finden Sie hier eine Erklärung, wie die TypeTokenKlasse verwendet wird. Die Konstruktion new TypeToken<...>() {}.getType()erfasst einen Typ zur Kompilierungszeit (zwischen <und >) in einem Laufzeitobjekt java.lang.reflect.Type. Im Gegensatz zu einem ClassObjekt, das nur einen rohen (gelöschten) Typ darstellen kann, kann das TypeObjekt einen beliebigen Typ in der Java-Sprache darstellen, einschließlich einer parametrisierten Instanziierung eines generischen Typs.

Die TypeTokenKlasse selbst hat keinen öffentlichen Konstruktor, da Sie ihn nicht direkt erstellen sollen. Stattdessen erstellen Sie immer eine anonyme Unterklasse (daher die {}, die ein notwendiger Bestandteil dieses Ausdrucks ist).

Aufgrund des Löschens von Typen kann die TypeTokenKlasse nur Typen erfassen, die zur Kompilierungszeit vollständig bekannt sind. (Das heißt, Sie können nicht new TypeToken<List<T>>() {}.getType()für einen Typparameter tun T.)

Weitere Informationen finden Sie in der Dokumentation zur TypeTokenKlasse .


31
In neuen Versionen von GSON ist der TypeToken-Konstruktor nicht öffentlich, daher wird hier ein nicht sichtbarer Konstruktorfehler angezeigt. Was müssen Sie in diesem Fall tun?
Pablo

8
Mit der aktuellen Version von GSON (2.2.4) funktioniert es wieder. Hier können Sie auf den Konstruktor zugreifen.

Mein JSON-Objekt beginnt mit einem Objekt und enthält dann ein Array des gewünschten Objekts { "myObjectArray":[ {....} , {....} , {....} ] }. Ich habe die Modelldatei erstellt {....}. Wie kann ich diesen generischen Auflistungscode dazu bringen, nicht anzunehmen, dass mein Stammelement ein Array ist, ohne eine neue verschachtelte Objektdatei zu erstellen?
CQM

7
Folgende Importe erforderlich --- import java.lang.reflect.Type; importieren com.google.gson.reflect.TypeToken
Umair Saleem

4
Dies ist gut, wenn YourClass im Code festgelegt ist. Was ist, wenn die Klasse zur Laufzeit kommt?
Jasxir

273

Eine andere Möglichkeit besteht darin, ein Array als Typ zu verwenden, z.

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

Auf diese Weise vermeiden Sie den Ärger mit dem Type-Objekt. Wenn Sie wirklich eine Liste benötigen, können Sie das Array jederzeit in eine Liste konvertieren, indem Sie:

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

IMHO ist dies viel besser lesbar.

Und um es zu einer tatsächlichen Liste zu machen (die geändert werden kann, siehe Einschränkungen von Arrays.asList()), gehen Sie einfach wie folgt vor:

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

4
das ist toll! Wie kann ich es mit Reflexion verwenden? Ich kenne den MyClassWert nicht und er wird dynamisch definiert!
Amin Sh

1
nota: Achten Sie dabei darauf, dass mcList keine vollständige Liste ist. Viele Dinge werden nicht funktionieren.
NJZK2

4
Wie benutzt man es mit Generika? T[] yourClassList = gson.fromJson(message, T[].class);// kann nicht aus Typvariable auswählen
Pawel Cioch

2
@MateusViccari zum Zeitpunkt dieses Kommentars war mcListin dieser Antwort nur das Ergebnis des Aufrufs an Arrays.asList. Diese Methode gibt eine Liste zurück, in der die meisten, wenn nicht alle optionalen Methoden nicht implementiert sind, und löst Ausnahmen aus. Beispielsweise können Sie dieser Liste kein Element hinzufügen. Wie aus der späteren Bearbeitung hervorgeht, Arrays.asListgibt es Einschränkungen, und ArrayListwenn Sie es in eine tatsächliche Version einbinden, können Sie eine Liste abrufen, die in vielen Fällen nützlicher ist.
njzk2

2
Wenn Sie zur Laufzeit einen Array-Typ für einen beliebigen Elementtyp erstellen müssen, können Sie diesen Array.newInstance(clazz, 0).getClass()wie in der Antwort von David Wood beschrieben verwenden .
Daniel Pryden

31

Siehe diesen Beitrag. Java Type Generic als Argument für GSON

Ich habe eine bessere Lösung dafür. Hier ist die Wrapper-Klasse für die Liste, damit der Wrapper den genauen Listentyp speichern kann.

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

Und dann kann der Code einfach sein:

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

Was ist mEntity.rulePattern?
Al Lelopath

Es ist nur ein Beispielobjekt zum Testen. Sie müssen sich nicht darum kümmern. Verwenden Sie die toList-Methode und alles läuft gut.
Glücklicher

@Happier Ich versuche dieses Gson 2.8.2 zu implementieren und es scheint nicht zu funktionieren. Jede Chance stackoverflow.com/questions/50743932/… Sie können einen Blick darauf werfen und mich wissen lassen, was ich vermisse
Praveen

1
@Praveen Ich habe es in 2.8.2 so versucht, es funktioniert wie im Original.
Glücklicher

30

Da Gson 2.8können wir util Funktion wie erstellen

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

Beispiel mit

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

2
TypeToken#getParameterizedsieht viel besser aus als der Hack mit einer anonymen Unterklasse
Nikolay Kulachenko

Dies sollte die akzeptierte Antwort sein. Zumindest für neuere Versionen
ccpizza

2
Dies ist die perfekte Antwort. Löst mein Problem.
Donmj

Ich habe Ihre Methode "wie sie ist" kopiert und sie funktioniert nicht: Der Compiler sagt "Die Methode getParameterized (Klasse <Liste>, Klasse <T>) ist für den Typ TypeToken undefiniert". Ich habe sowohl meine Gson-Version (2.8.0) als auch die Dokumentation überprüft und auf dieser Seite ist alles in Ordnung ... Am Ende habe ich die @ Happy-Lösung verwendet, die gut funktioniert
Leguminator

@leguminator hast du TypeToken richtig importiert? und Sie verwenden Java oder Kotlin. Ich werde versuchen, es erneut zu testen
Phan Van Linh

26

Wep, ein weiterer Weg, um das gleiche Ergebnis zu erzielen. Wir verwenden es für seine Lesbarkeit.

Anstatt diesen schwer lesbaren Satz zu machen:

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

Erstellen Sie eine leere Klasse, die eine Liste Ihres Objekts erweitert:

public class YourClassList extends ArrayList<YourClass> {}

Und verwenden Sie es beim Parsen des JSON:

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

9

Für Kotlin einfach:

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

oder hier ist eine nützliche Funktion:

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

Dann zu verwenden:

val type = typeOfList<YourMagicObject>()

Ich habe Ihren Code verwendet, um diese Funktion mit reifizierten Typen zu erstellen: inline fun <reified T> buildType() = object : TypeToken<T>() {}.type!!und rufen Sie sie mit dem Listentyp auf:buildType<List<YourMagicObject>>()
coffeemakr

@coffeemakr Du brauchst hier keine reifizierten Typen.
Chad Bingham

Oh. Aber warum erstellen Sie das Typ-Token einer ArrayList in buildTypeund rufen die Funktion auch mit dem generischen Typ auf? Ist das ein Tippfehler? - Das würde Arraylist <Arraylist <YourMagicObject >> erstellen
coffeemakr

@coffeemakr ah, ja. Tippfehler
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);
}

Beispiel:

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

Gut. Aber dies dupliziert die DevNGobige Antwort, die 2 Jahre zuvor geschrieben wurde: stackoverflow.com/a/17300003/1339923 (und lesen Sie diese Antwort für Vorbehalte gegen diesen Ansatz)
Lambart

6

Da es meine ursprüngliche Frage beantwortet, habe ich die Antwort von doc_180 akzeptiert, aber wenn jemand erneut auf dieses Problem stößt, werde ich auch die 2. Hälfte meiner Frage beantworten:

Der von mir beschriebene NullPointerError hatte nichts mit der Liste selbst zu tun, sondern mit ihrem Inhalt!

Die "MyClass" -Klasse hatte keinen "no args" -Konstruktor und auch keine Superklasse. Nachdem ich MyClass und seiner Oberklasse einen einfachen Konstruktor "MyClass ()" hinzugefügt hatte, funktionierte alles einwandfrei, einschließlich der von doc_180 vorgeschlagenen Serialisierung und Deserialisierung der Liste.


1
Wenn Sie eine Liste abstrakter Klassen haben, wird der gleiche Fehler angezeigt. Ich denke, dies ist die allgemeine Fehlermeldung von GSON für "Klasse kann nicht instanziiert werden".
Drew

Der Tipp zum Hinzufügen eines Konstruktors half mir zu erkennen, warum ich alle Nullwerte hatte. Ich hatte Feldnamen wie "To" und "From" in meiner JSON-Zeichenfolge, aber die entsprechenden Felder in meinem Objekt waren "to" und "from" in Kleinbuchstaben, so dass sie übersprungen wurden
Rune

4

Hier ist eine Lösung, die mit einem dynamisch definierten Typ arbeitet. Der Trick besteht darin, mit Array.newInstance () den richtigen Array-Typ zu erstellen.

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

Diese Antwort wäre besser, wenn Sie class.cast()die ungeprüfte Warnung vermeiden würden, die durch das Casting an verursacht wird (T). Oder, noch besser, kümmern Sie sich nicht um das Casting und verwenden Sie so etwas wie Arrays.asList()das Konvertieren von einem Array in ein List<T>. Es ist auch nicht erforderlich, eine Länge an zu übergeben Array.newInstance()- ein Array der Größe Null ist gut genug, um es aufzurufen getClass().
Daniel Pryden

Vielen Dank für den Vorschlag, ich habe Ihre Änderungsvorschläge gemacht und den obigen Beitrag aktualisiert.
David Wood

2

Ich möchte noch eine Möglichkeit hinzufügen. Wenn Sie TypeToken nicht verwenden und das JSON-Objektarray in eine ArrayList konvertieren möchten, können Sie wie folgt vorgehen:

Wenn Ihre JSON-Struktur wie folgt aussieht:

{

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

}}

und Ihre Klassenstruktur ist wie folgt:

public class ClassName implements Parcelable {

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

dann können Sie es wie folgt analysieren:

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

Jetzt können Sie auf jedes Element des className-Objekts zugreifen.


1

In Beispiel 2 finden Sie Informationen zum Verständnis der Klasse 'Type' von Gson.

Beispiel 1: In diesem deserilizeResturant haben wir das Array Employee [] verwendet und die Details abgerufen

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

Beispiel 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

Ich mochte die Antwort von kays1, konnte sie aber nicht implementieren. Also habe ich meine eigene Version mit seinem Konzept gebaut.

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

Verwendungszweck:

List<MyClass> MyList= JsonListHelper.getList(jsonArrayString);

Dies kann sicherlich nicht funktionieren, da Sie versuchen, T in der Kompilierungszeit zu verwenden. Dies wird effektiv zu einer Liste von StringMap deserialisieren, nicht wahr?
JHH

0

In meinem Fall hat die Antwort von @ uncaught_exceptions nicht funktioniert. Ich musste List.classstattdessen Folgendes verwenden java.lang.reflect.Type:

String jsonDuplicatedItems = request.getSession().getAttribute("jsonDuplicatedItems").toString();
List<Map.Entry<Product, Integer>> entries = gson.fromJson(jsonDuplicatedItems, List.class);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.