Gson TypeToken mit dynamischem ArrayList-Elementtyp

76

Ich habe diesen Code:

Type typeOfObjectsList = new TypeToken<ArrayList<myClass>>() {}.getType();
List<myClass> objectsList = new Gson().fromJson(json, typeOfObjectsList);

Es konvertiert eine JSON-Zeichenfolge in ein ListObjekt. Aber jetzt möchte ich dies ArrayListmit einem dynamischen Typ (nicht nur myClass) haben, der zur Laufzeit definiert wird.

Der ArrayListArtikeltyp des wird mit Reflexion definiert .

Ich habe es versucht:

    private <T> Type setModelAndGetCorrespondingList2(Class<T> type) {
        Type typeOfObjectsListNew = new TypeToken<ArrayList<T>>() {}.getType();
        return typeOfObjectsListNew;
    }

Aber es funktioniert nicht. Dies ist die Ausnahme:

java.sql.SQLException: Fail to convert to internal representation: {....my json....}
Amin Sh
quelle
1
Das ist eine Ausnahme. Es hat nichts mit dem Code zu tun, den Sie gepostet haben. Zeigen Sie uns den JDBC-Code.
Sotirios Delimanolis
1
@SotiriosDelimanolis Es ist nicht! Ich möchte nur, dass dieser TypeToken<ArrayList<T>>()Code den dynamischen Arraylist-Typ akzeptiert. so etwas wie das:TypeToken<ArrayList<Class.forName(MyClass)>>
Amin Sh

Antworten:

49

Die von Ihnen vorgeschlagene Syntax ist ungültig. Folgende

new TypeToken<ArrayList<Class.forName(MyClass)>>

ist ungültig, weil Sie versuchen, einen Methodenaufruf zu übergeben, bei dem ein Typname erwartet wird.

Folgende

new TypeToken<ArrayList<T>>() 

ist nicht möglich, da Generika (Typ Löschung) und Reflexion funktionieren. Der ganze TypeTokenHack funktioniert, weil er Class#getGenericSuperclass()folgendes tut

Gibt den Typ zurück, der die direkte Oberklasse der Entität (Klasse, Schnittstelle, primitiver Typ oder void) darstellt, die von dieser Klasse dargestellt wird.

Wenn es sich bei der Oberklasse um einen parametrisierten Typ handelt, muss das zurückgegebene Type-Objekt die im Quellcode verwendeten tatsächlichen Typparameter genau wiedergeben.

Mit anderen Worten, wenn es angezeigt ArrayList<T>wird, ParameterizedTypewird es zurückgegeben, und Sie können den Kompilierungszeitwert, den die Typvariable Tgehabt hätte, nicht extrahieren .

Typeund ParameterizedTypesind beide Schnittstellen. Sie können eine Instanz Ihrer eigenen Implementierung bereitstellen.

Sotirios Delimanolis
quelle
Ich denke, es ist möglich, mit der hier gezeigten Technik: stackoverflow.com/questions/14139437/…
Benoit Duffez
2
@ BenoitDuffez Vorsicht. Die Antwort, die Sie verlinkt haben, ist etwas anderes. Es wird ein tatsächliches ClassObjekt verwendet, sodass Gson sehr gut wissen kann, auf welchen Typ es deserialisiert werden soll. In der Frage des OP möchten sie dies ohne Verwendung des ClassObjekts und nur über das Argument type tun , das in der Methode selbst nicht verfügbar ist.
Sotirios Delimanolis
@SotiriosDelimanolis: Du hast absolut recht. Ich dachte, dass es ein akzeptabler Preis ist, die Klasse zum Laufen zu bringen, damit dies funktioniert. Tatsächlich verknüpft die verknüpfte Antwort jedoch etwas anderes als das, was das OP verlangt hat. Danke für die Klarstellung.
Benoit Duffez
29

Option 1 - Implementieren java.lang.reflect.ParameterizedTypeSie sich und geben Sie es an Gson weiter.

private static class ListParameterizedType implements ParameterizedType {

    private Type type;

    private ListParameterizedType(Type type) {
        this.type = type;
    }

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

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

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

    // implement equals method too! (as per javadoc)
}

Dann einfach:

Type type = new ListParameterizedType(clazz);
List<T> list = gson.fromJson(json, type);

Beachten Sie, dass gemäß javadoc auch die Methode equals implementiert werden sollte.

Option 2 - (nicht tun) gson intern wiederverwenden ...

Dies wird auch funktionieren, zumindest mit Gson 2.2.4.

Type type = com.google.gson.internal.$Gson$Types.newParameterizedTypeWithOwner(null, ArrayList.class, clazz);
MateSzvoboda
quelle
8

Das hat bei mir funktioniert:

public <T> List<T> listEntity(Class<T> clazz)
        throws WsIntegracaoException {
    try {
        // Consuming remote method
        String strJson = getService().listEntity(clazz.getName());

        JsonParser parser = new JsonParser();
        JsonArray array = parser.parse(strJson).getAsJsonArray();

        List<T> lst =  new ArrayList<T>();
        for(final JsonElement json: array){
            T entity = GSON.fromJson(json, clazz);
            lst.add(entity);
        }

        return lst;

    } catch (Exception e) {
        throw new WsIntegracaoException(
                "WS method error [listEntity()]", e);
    }
}
Rodrigo Tavares
quelle
Ihr Code mit 'public <T>' in der Methode funktioniert mit generischen Java-Methoden, die hier dokumentiert sind: docs.oracle.com/javase/tutorial/extra/generics/methods.html
ʔ ᵔᴥᵔ ʔ
7

Sie können dies mit Guavas mächtigerem tun TypeToken:

private static <T> Type setModelAndGetCorrespondingList2(Class<T> type) {
    return new TypeToken<ArrayList<T>>() {}
            .where(new TypeParameter<T>() {}, type)
            .getType();
}
shmosel
quelle
Ich habe Guava bereits in meinem Projekt verwendet. Danke, funktioniert perfekt.
Betrunkener Papa
5

sun.reflect.generics.reflectiveObjects.ParameterizedTypeImplarbeitet. Keine benutzerdefinierte Implementierung erforderlich

Type type = ParameterizedTypeImpl.make(List.class, new Type[]{childrenClazz}, null);
List list = gson.fromJson(json, type);

Kann mit Karten und jeder anderen Sammlung verwendet werden:

ParameterizedTypeImpl.make(Map.class, new Type[]{String.class, childrenClazz}, null);

Hier ist eine nette Demo, wie Sie sie in einem benutzerdefinierten Deserializer verwenden können: Deserializing ImmutableList using Gson

Varren
quelle
2

Sie können es tatsächlich tun. Sie müssen nur zuerst Ihre Daten in ein JsonArrayObjekt analysieren und dann jedes Objekt einzeln transformieren und zu einem hinzufügen List:

Class<T> dataType;

//...

JsonElement root = jsonParser.parse(json);
List<T> data = new ArrayList<>();
JsonArray rootArray = root.getAsJsonArray();
for (JsonElement json : rootArray) {
    try {
        data.add(gson.fromJson(json, dataType));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
return data;
Ovidiu Latcu
quelle
try / catch inside loop ist sehr ineffizient.
Dobrivoje
try / catch innerhalb oder außerhalb der Schleife hat keinen Einfluss auf die Leistung.
Carson Holzheimer
0

Voll funktionsfähige Lösung:

String json = .... //example: mPrefs.getString("list", "");
ArrayList<YouClassName> myTypes = fromJSonList(json, YouClassName.class);


public <YouClassName> ArrayList<YouClassName> fromJSonList(String json, Class<YouClassName> type) {
        Gson gson = new Gson();
        return gson.fromJson(json, TypeToken.getParameterized(ArrayList.class, type).getType());
    }
TheLogicGuy
quelle
0

Mit kotlin können Sie die folgenden Funktionen verwenden, um (von / nach) alle (JsonArray / JsonObject) nur in einer Zeile zu konvertieren, ohne ein TypeToken senden zu müssen: -

Konvertieren Sie eine Klasse oder ein Array in eine JSON-Zeichenfolge

inline fun <reified T : Any> T?.json() = this?.let { Gson().toJson(this, T::class.java) }

Zu verwendendes Beispiel:

 val list = listOf("1","2","3")
 val jsonArrayAsString = list.json() 
 //output : ["1","2","3"]

 val model= Foo(name = "example",email = "[email protected]") 
 val jsonObjectAsString = model.json()
//output : {"name" : "example", "email" : "[email protected]"}

Konvertieren Sie die JSON-Zeichenfolge in eine beliebige Klasse oder ein beliebiges Array

inline fun <reified T : Any> String?.fromJson(): T? = this?.let {
    val type = object : TypeToken<T>() {}.type
    Gson().fromJson(this, type)
}

Zu verwendendes Beispiel:

 val jsonArrayAsString = "[\"1\",\"2\",\"3\"]"
 val list = jsonArrayAsString.fromJson<List<String>>()

 val jsonObjectAsString = "{ "name" : "example", "email" : "t@t.com"}"
 val model : Foo? = jsonObjectAsString.fromJson() 
 //or 
 val model = jsonObjectAsString.fromJson<Foo>() 
Ibrahim Muhamad
quelle