web-dev-qa-db-fra.com

Type Java générique comme argument pour GSON

Dans GSON pour obtenir une liste d'objets que vous faites

Gson gson = new Gson();
Type token = new TypeToken<List<MyType>>(){}.getType();
return gson.fromJson(json, token);

Cela fonctionne très bien, mais je veux aller plus loin et paramétrer MyType afin que je puisse avoir une fonction commune pour analyser la liste d'objets avec ce code.

// the common function 
public <T> List<T> fromJSonList(String json, Class<T> type) {
  Gson gson = new Gson();
  Type collectionType = new TypeToken<List<T>>(){}.getType();
  return gson.fromJson(json, collectionType);
}

// the call
List<MyType> myTypes = parser.fromJSonList(jsonString, MyType.class);

Retourne malheureusement un tableau de StringMaps, pas le type. T est interprété comme un autre type générique, pas mon type. Une solution de contournement?

45
Rodrigo Asensio

Les génériques travaillent au moment de la compilation. La raison pour laquelle les jetons de super-type fonctionnent, c'est parce que les classes internes (anonymes) peuvent accéder aux arguments de type de leurs superclasses génériques (superinterfaces), qui sont à leur tour stockées directement dans les métadonnées de code intermédiaire.

Une fois votre fichier source .Java compilé, le paramètre de type <T> est évidemment jeté. Comme il n’est pas connu au moment de la compilation, il ne peut pas être stocké en bytecode, il est donc effacé et Gson ne peut pas le lire.

METTRE &AGRAVE; JOUR

Après la réponse de newacct, j’ai essayé de mettre en œuvre ce qu’il avait suggéré dans son option 2, c’est-à-dire de mettre en œuvre une ParameterizedType. Le code ressemble à ceci (voici une base test ):

class ListOfSomething<X> implements ParameterizedType {

    private Class<?> wrapped;

    public ListOfSomething(Class<X> wrapped) {
        this.wrapped = wrapped;
    }

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

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

    public Type getOwnerType() {
        return null;
    }

}

le but de ce code doit être utilisé dans getFromJsonList():

public List<T> fromJsonList(String json, Class<T> klass) {
    Gson gson = new Gson();
    return gson.fromJson(json, new ListOfSomething<T>(klass));
}

Même si la technique fonctionne et est très astucieuse (je ne le savais pas et je n'y aurais jamais pensé), voici le dernier exploit:

List<Integer> list = new Factory<Integer>()
         .getFromJsonList(text, Integer.class)

au lieu de

List<Integer> list = new Gson().fromJson(text,
         new TypeToken<List<Integer>>(){}.getType());

Pour moi, tout cela est inutile, même si je conviens que TypeTokens rend le code méchant: P

42
Raffaele

Depuis gson 2.8.0, vous pouvez utiliser TypeToken#getParametized((Type rawType, Type... typeArguments)) pour créer la typeToken, alors getType() devrait suffire.

Par exemple:

TypeToken.getParameterized(List.class, myType).getType();
28
oldergod
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);
}

Exemple:

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

J'ai pris un peu plus loin l'approche de Raffaele et ai généralisé la classe, de sorte qu'elle fonctionne avec toutes les classes A, où B est une classe non paramétrée. Pourrait être utile pour les ensembles et autres collections.

    public class GenericOf<X, Y> implements ParameterizedType {

    private final Class<X> container;
    private final Class<Y> wrapped;

    public GenericOf(Class<X> container, Class<Y> wrapped) {
        this.container = container;
        this.wrapped = wrapped;
    }

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

    public Type getRawType() {
        return container;
    }

    public Type getOwnerType() {
        return null;
    }

}
6

Cela a été répondu dans les questions précédentes. En gros, il y a 2 options:

  1. Transmettez la Type du site appelant. Le code appelant utilisera TypeToken ou quoi que ce soit pour le construire.
  2. Construisez vous-même une Type correspondant au type paramétré. Cela vous demandera d’écrire une classe qui implémente ParameterizedType
3
newacct

Si vous programmez en kotlin, nous pouvons utiliser reified type parameter dans la fonction inline

class GenericGson {

    companion object {
        inline fun <reified T : Any> Gson.fromJsonTokenType(jsonString: String): T {
            val type = object : TypeToken<T>() {}.type
            return this.fromJson(jsonString, type)
        }

        inline fun <reified T : Any> Gson.fromJsonType(jsonString: String): T = this.fromJson(jsonString, T::class.Java)

        inline fun <reified T : Any> fromJsonTokenType(jsonString: String): T = Gson().fromJsonTokenType(jsonString)

        inline fun <reified T : Any> fromJsonType(jsonString: String): T = Gson().fromJsonType(jsonString)
    }
}

Et utilisez comme ci-dessous dans votre code

val arrayList = GenericGson.fromJsonTokenType<ArrayList<Person>>(json)
2
alijandro

Voici la base de code complète sur l'excellente réponse de @oldergod 

public <T> List<T> fromJSonList(String json, Class<T> myType) {
    Gson gson = new Gson();
    Type collectionType = TypeToken.getParameterized(List.class, myType).getType();
    return gson.fromJson(json, collectionType);
}

En utilisant

List<MyType> myTypes = parser.fromJSonList(jsonString, MyType.class);

J'espère que ça aide

1
Linh

En kotlin simple utiliser par exemple:

Obtient des lieux

fun getPlaces<T> (jsonString : String, clazz: Class<T>) : T { val places : T = Gson().fromJson(jsonString,clazz) return places }

Ensuite, vous pouvez utiliser comme:

val places = getPlaces(Array<Place>::class.Java)
0
Cristian Cardoso

La solution "ListOfSomething" de Kotlin qui a fonctionné pour moi:

fun <T: Any> getGsonList(json: String, kclass: KClass<T>) : List<T> {

    return getGsonInstance().fromJson<List<T>>(json, ListOfSomething<T>(kclass.Java))
}


internal class ListOfSomething<X>(wrapped: Class<X>) : ParameterizedType {

    private val wrapped: Class<*>

    init {
        this.wrapped = wrapped
    }

    override fun getActualTypeArguments(): Array<Type> {
        return arrayOf<Type>(wrapped)
    }

    override fun getRawType(): Type {
        return ArrayList::class.Java
    }

    override fun getOwnerType(): Type? {
        return null
    }
}
0
Michael Peterson