Google Gson - désérialiser l'objet <classe> de la liste? (type générique)

441

Je souhaite transférer un objet liste via Google Gson, mais je ne sais pas comment désérialiser des types génériques.

Ce que j'ai essayé après avoir regardé cela (réponse de BalusC):

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

mais alors j'obtiens une erreur dans l'éclipse disant "Le type nouveau List () {} doit implémenter la méthode abstraite héritée ..." et si j'utilise une solution rapide, j'obtiens un monstre de plus de 20 stubs de méthode.

Je suis presque sûr qu'il existe une solution plus simple, mais il me semble impossible de la trouver!

Éditer:

Maintenant j'ai

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

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

Cependant, j'obtiens l'exception suivante sur la ligne "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)

Je fais JsonParseExceptions de capture et « résultat » est non nul.

J'ai vérifié listType avec le débogueur et j'ai obtenu ce qui suit:

  • type de liste
    • args = ListOfTypes
      • list = null
      • olvedTypes = Type [1]
    • loader = PathClassLoader
    • ownerType0 = null
    • ownerTypeRes = null
    • rawType = Classe (java.util.ArrayList)
    • rawTypeName = "java.util.ArrayList"

il semble donc que l'invocation "getClass" ne fonctionne pas correctement. Aucune suggestion...?

Edit2: I'v vérifié sur le Guide de l'utilisateur Gson . Il mentionne une exception d'exécution qui devrait se produire lors de l'analyse d'un type générique vers Json. Je l'ai fait "mal" (non illustré ci-dessus), comme dans l'exemple, mais je n'ai pas du tout obtenu cette exception. J'ai donc changé la sérialisation comme dans le guide d'utilisation suggéré. Mais ça n'a pas aidé.

Edit3: Résolu, voir ma réponse ci-dessous.

méduse
la source
1
La réponse que vous avez pointée utilise TokenType. Avez-vous essayé de cette façon?
Nishant
vient de recevoir le même indice qu'une réponse. la prochaine fois, je donnerai l'exemple de plus près. ;)
méduses
Pouvez-vous essayer une implémentation de list in type token? Puisque votre type brut est une liste de tableaux, vous devez essayer la liste de tableaux.
uncaught_exceptions

Réponses:

954

Méthode pour désérialiser la collection générique:

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

Étant donné que plusieurs personnes dans les commentaires l'ont mentionné, voici une explication de la façon dont la TypeTokenclasse est utilisée. La construction new TypeToken<...>() {}.getType()capture un type au moment de la compilation (entre le <et >) dans un java.lang.reflect.Typeobjet d' exécution . Contrairement à un Classobjet, qui ne peut représenter qu'un type brut (effacé), l' Typeobjet peut représenter n'importe quel type en langage Java, y compris une instanciation paramétrée d'un type générique.

La TypeTokenclasse elle-même n'a pas de constructeur public, car vous n'êtes pas censé le construire directement. Au lieu de cela, vous construisez toujours une sous-classe anonyme (d'où le {}, qui est une partie nécessaire de cette expression).

En raison de l'effacement des types, la TypeTokenclasse ne peut capturer que les types entièrement connus au moment de la compilation. (Autrement dit, vous ne pouvez pas faire new TypeToken<List<T>>() {}.getType()pour un paramètre de type T.)

Pour plus d'informations, consultez la documentation de la TypeTokenclasse .

uncaught_exceptions
la source
31
Dans les nouvelles versions de GSON, le constructeur TypeToken n'est pas public, donc vous obtenez ici une erreur de constructeur non visible. Que devez-vous faire dans ce cas?
Pablo
8
En utilisant la version actuelle de GSON (2.2.4), cela fonctionne à nouveau. Vous pouvez accéder au constructeur ici.
mon objet json commence par un objet, puis contient un tableau de l'objet que je veux { "myObjectArray":[ {....} , {....} , {....} ] }, j'ai créé le fichier modèle {....}, comment puis-je obtenir ce code de collection générique pour ne pas supposer que mon élément racine est un tableau sans créer un nouveau fichier objet imbriqué
CQM
7
Importations suivantes requises --- import java.lang.reflect.Type; import com.google.gson.reflect.TypeToken
Umair Saleem
4
C'est bien si YourClass est fixé dans le code. Et si la classe arrive au moment de l'exécution?
jasxir
273

Une autre façon consiste à utiliser un tableau comme type, par exemple:

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

De cette façon, vous évitez tous les tracas avec l'objet Type, et si vous avez vraiment besoin d'une liste, vous pouvez toujours convertir le tableau en liste en:

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

À mon humble avis, c'est beaucoup plus lisible.

Et pour en faire une liste réelle (qui peut être modifiée, voir les limitations de Arrays.asList()), procédez comme suit:

List<MyClass> mcList = new ArrayList<>(Arrays.asList(mcArray));
DevNG
la source
4
c'est bien! Comment puis-je l'utiliser avec la réflexion? Je ne connais pas la MyClassvaleur et elle sera définie dynamiquement!
Amin Sh
1
nota: avec cela, faites attention à ce que mcList ne soit pas une liste à part entière. beaucoup de choses ne fonctionneront pas.
njzk2
4
Comment l'utiliser avec des génériques? T[] yourClassList = gson.fromJson(message, T[].class);// ne peut pas sélectionner la variable de type
Pawel Cioch
2
@MateusViccari au moment de ce commentaire, mcListdans cette réponse n'était que le résultat de l'appel à Arrays.asList. Cette méthode renvoie une liste sur laquelle la plupart des méthodes facultatives, sinon toutes, ne sont pas implémentées et lèvent des exceptions. Par exemple, vous ne pouvez ajouter aucun élément à cette liste. Comme le suggère la dernière modification, il Arrays.asLista ses limites et son encapsulation dans un réel ArrayListvous permet d'obtenir une liste plus utile dans de nombreux cas.
njzk2
2
Si vous devez construire un type de tableau au moment de l'exécution pour un type d'élément arbitraire, vous pouvez utiliser Array.newInstance(clazz, 0).getClass()comme décrit dans la réponse de David Wood .
Daniel Pryden
31

Référez-vous à ce post. Type Java générique comme argument pour GSON

J'ai une meilleure solution pour cela. Voici la classe wrapper pour list afin que le wrapper puisse stocker exactement le type de liste.

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

Et puis, le code peut être simple:

public static <T> List<T> toList(String json, Class<T> typeClass)
{
    return sGson.fromJson(json, new ListOfJson<T>(typeClass));
}
Plus heureux
la source
Qu'est-ce que c'est mEntity.rulePattern?
Al Lelopath
Ce n'est qu'un exemple d'objet à tester. Vous n'avez pas besoin de vous en soucier. Utilisez la méthode toList et tout se passe bien.
Plus heureux le
@Happier J'essaie d'implémenter ce Gson 2.8.2 et il semble ne pas fonctionner. Toute chance stackoverflow.com/questions/50743932/… vous pouvez jeter un oeil et me faire savoir ce que je manque
Praveen
1
@Praveen J'ai essayé de cette façon en 2.8.2, cela fonctionne comme original.
Plus heureux le
31

Depuis Gson 2.8, nous pouvons créer une fonction util comme

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

Exemple utilisant

String jsonArray = ...
List<User> user = getList(jsonArray, User.class);
Phan Van Linh
la source
2
TypeToken#getParameterizedsemble mieux que le hack avec une sous
Nikolay Kulachenko
ce devrait être la réponse acceptée; au moins pour les versions plus récentes
ccpizza
2
Ceci est la réponse parfaite. Résout mon problème.
donmj
J'ai copié votre méthode "en l'état" et cela ne fonctionne pas: le compilateur dit "La méthode getParameterized (Class <List>, Class <T>) n'est pas définie pour le type TypeToken". J'ai vérifié à la fois ma version Gson (2.8.0) et la documentation et tout va bien de ce côté ... J'ai fini par utiliser la solution @Happier qui fonctionne bien
léguminateur le
@leguminator avez-vous importé TypeToken correctement? et vous utilisez java ou kotlin. Je vais essayer de tester à nouveau
Phan Van Linh
26

Wep, une autre façon d'obtenir le même résultat. Nous l'utilisons pour sa lisibilité.

Au lieu de faire cette phrase difficile à lire:

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

Créez une classe vide qui étend une liste de votre objet:

public class YourClassList extends ArrayList<YourClass> {}

Et utilisez-le pour analyser le JSON:

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

Pour Kotlin, simplement:

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

ou, voici une fonction utile:

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

Ensuite, pour utiliser:

val type = typeOfList<YourMagicObject>()
Chad Bingham
la source
J'ai utilisé votre code pour créer cette fonction en utilisant des types réifiés: inline fun <reified T> buildType() = object : TypeToken<T>() {}.type!!et appelez-la avec le type List:buildType<List<YourMagicObject>>()
coffeemakr
@coffeemakr Vous n'avez pas besoin de types réifiés ici.
Chad Bingham
Oh. Mais pourquoi créez-vous le jeton de type d'un ArrayList dans buildTypeet appelez-vous également la fonction avec le type générique? Est-ce une faute de frappe? - Cela créerait ArrayList <ArrayList <YourMagicObject>>
coffeemakr
@coffeemakr ah, oui. Typo
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);
}

Exemple:

getList(MyClass[].class, "[{...}]");
kayz1
la source
Bon. Mais cela reproduit la DevNGréponse ci-dessus, écrite 2 ans plus tôt: stackoverflow.com/a/17300003/1339923 (et lisez cette réponse pour les mises en garde à cette approche)
Lambart
6

Comme il répond à ma question d'origine, j'ai accepté la réponse du doc_180, mais si quelqu'un rencontre à nouveau ce problème, je répondrai également à la 2e moitié de ma question:

Le NullPointerError que j'ai décrit n'avait rien à voir avec la liste elle-même, mais avec son contenu!

La classe "MyClass" n'avait pas de constructeur "no args", et ni l'un ni l'autre n'avait sa superclasse. Une fois que j'ai ajouté un simple constructeur "MyClass ()" à MyClass et à sa superclasse, tout a bien fonctionné, y compris la sérialisation et la désérialisation de liste comme suggéré par doc_180.

méduse
la source
1
Si vous avez une liste de classes abstraites, vous obtiendrez la même erreur. Je suppose que c'est le message d'erreur général de GSON pour "Impossible d'instancier la classe".
Tiré le
L'astuce sur l'ajout d'un constructeur m'a aidé à comprendre pourquoi j'avais toutes des valeurs nulles. J'avais des noms de champs comme "À" et "De" dans ma chaîne JSON, mais les champs correspondants dans mon objet étaient "à" et "de" en minuscules, donc ils ont été ignorés
Rune
4

Voici une solution qui fonctionne avec un type défini dynamiquement. L'astuce consiste à créer le type de tableau approprié à l'aide de 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
la source
Cette réponse serait meilleure si vous aviez l'habitude class.cast()d'éviter l'avertissement incontrôlé provoqué par la conversion vers (T). Ou, mieux encore, ne vous embêtez pas avec la Arrays.asList()conversion et utilisez quelque chose comme pour convertir d'un tableau en a List<T>. De plus, pas besoin de passer une longueur à Array.newInstance()- un tableau de taille zéro sera assez bon pour faire appel getClass().
Daniel Pryden
Merci pour la suggestion, j'ai apporté vos modifications suggérées et mis à jour le message ci-dessus.
David Wood
2

Je veux ajouter pour une possibilité de plus. Si vous ne souhaitez pas utiliser TypeToken et que vous souhaitez convertir un tableau d'objets json en ArrayList, vous pouvez procéder comme suit:

Si votre structure json est comme:

{

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

}

et votre structure de classe est comme:

public class ClassName implements Parcelable {

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

alors vous pouvez l'analyser comme:

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

Vous pouvez maintenant accéder à chaque élément de l'objet className.

Apurva Sharma
la source
1

Reportez-vous à l'exemple 2 pour la compréhension de la classe «Type» de Gson.

Exemple 1: dans ce deserilizeResturant, nous avons utilisé le tableau Employee [] et obtenu les détails

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

Exemple 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
la source
0

J'ai aimé la réponse de kays1 mais je n'ai pas pu l'implémenter. J'ai donc construit ma propre version en utilisant son concept.

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

Usage:

List<MyClass> MyList= JsonListHelper.getList(jsonArrayString);
mike83_dev
la source
Cela ne peut certainement pas fonctionner puisque vous essayez d'utiliser T au moment de la compilation. Cela désérialisera efficacement une liste de StringMap, non?
JHH
0

Dans mon cas, la réponse de @ uncaught_exceptions n'a pas fonctionné, j'ai dû utiliser à la List.classplace de java.lang.reflect.Type:

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