J'ai un problème pour désérialiser une chaîne json avec Gson. Je reçois un éventail de commandes. La commande peut être start, stop, un autre type de commande. Naturellement, j'ai un polymorphisme et la commande start / stop hérite de la commande.
Comment puis-je le sérialiser à nouveau dans l'objet de commande correct à l'aide de gson?
Il semble que je n'obtiens que le type de base, c'est-à-dire le type déclaré et jamais le type d'exécution.
java
json
polymorphism
gson
deserialization
Sophie
la source
la source
Réponses:
C'est un peu tard mais j'ai dû faire exactement la même chose aujourd'hui. Donc, sur la base de mes recherches et lors de l'utilisation de gson-2.0, vous ne voulez vraiment pas utiliser la méthode registerTypeHierarchyAdapter , mais plutôt le registerTypeAdapter plus banal . Et vous n'avez certainement pas besoin de faire des instances ou d'écrire des adaptateurs pour les classes dérivées: juste un adaptateur pour la classe de base ou l'interface, à condition bien sûr que vous soyez satisfait de la sérialisation par défaut des classes dérivées. Quoi qu'il en soit, voici le code (package et importations supprimés) (également disponible dans github ):
La classe de base (interface dans mon cas):
Les deux classes dérivées, Cat:
Et chien:
Le IAnimalAdapter:
Et la classe Test:
Lorsque vous exécutez Test :: main, vous obtenez la sortie suivante:
J'ai également fait ce qui précède en utilisant la méthode registerTypeHierarchyAdapter , mais cela semblait nécessiter l'implémentation de classes de sérialiseur / désérialiseur DogAdapter et CatAdapter personnalisées, ce qui est difficile à gérer chaque fois que vous souhaitez ajouter un autre champ à Dog ou à Cat.
la source
Gson dispose actuellement d'un mécanisme pour enregistrer un adaptateur de hiérarchie de types qui peut être configuré pour une simple désérialisation polymorphe, mais je ne vois pas comment c'est le cas, car un adaptateur de hiérarchie de types semble être simplement un créateur de sérialiseur / désérialiseur / instance combiné, laissant les détails de la création de l'instance au codeur, sans fournir d'enregistrement de type polymorphe réel.
Il semble que Gson aura bientôt la
RuntimeTypeAdapter
désérialisation polymorphe plus simple. Voir http://code.google.com/p/google-gson/issues/detail?id=231 pour plus d'informations.Si l'utilisation du nouveau
RuntimeTypeAdapter
n'est pas possible et que vous devez utiliser Gson, je pense que vous devrez lancer votre propre solution, en enregistrant un désérialiseur personnalisé en tant qu'adaptateur de hiérarchie de types ou en tant qu'adaptateur de type. Voici un exemple.la source
RuntimeTypeAdapter
est maintenant terminé, malheureusement, il ne semble pas encore être dans le noyau de Gson. :-(Marcus Junius Brutus a eu une excellente réponse (merci!). Pour étendre son exemple, vous pouvez rendre sa classe d'adaptateur générique pour qu'elle fonctionne pour tous les types d'objets (pas seulement IAnimal) avec les modifications suivantes:
Et dans la classe de test:
la source
GSON a un très bon cas de test ici montrant comment définir et enregistrer un adaptateur de hiérarchie de types.
http://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java?r=739
Pour l'utiliser, procédez comme suit:
La méthode de sérialisation de l'adaptateur peut être une vérification if-else en cascade du type à sérialiser.
La désérialisation est un peu piratée. Dans l'exemple de test unitaire, il vérifie l'existence d'attributs révélateurs pour décider dans quelle classe désérialiser. Si vous pouvez modifier la source de l'objet que vous sérialisez, vous pouvez ajouter un attribut 'classType' à chaque instance qui contient le FQN du nom de la classe d'instance. Ceci est cependant très peu orienté objet.
la source
Google a publié son propre RuntimeTypeAdapterFactory pour gérer le polymorphisme, mais malheureusement, il ne fait pas partie du noyau gson (vous devez copier et coller la classe dans votre projet).
Exemple:
Ici, j'ai posté un exemple de travail complet en utilisant les modèles Animal, Dog et Cat.
Je pense qu'il est préférable de s'appuyer sur cet adaptateur plutôt que de le réimplémenter à partir de zéro.
la source
Le temps a passé, mais je n'ai pas pu trouver une très bonne solution en ligne. Voici une petite torsion sur la solution de @ MarcusJuniusBrutus, qui évite la récursivité infinie.
Gardez le même désérialiseur, mais supprimez le sérialiseur -
Ensuite, dans votre classe d'origine, ajoutez un champ avec
@SerializedName("CLASSNAME")
. L'astuce consiste maintenant à initialiser cela dans le constructeur de la classe de base , alors transformez votre interface en une classe abstraite.La raison pour laquelle il n'y a pas de récursivité infinie ici est que nous passons la classe d'exécution réelle (c'est-à-dire Dog not IAnimal) à
context.deserialize
. Cela n'appellera pas notre adaptateur de type, tant que nous utilisonsregisterTypeAdapter
et nonregisterTypeHierarchyAdapter
la source
Réponse mise à jour - Meilleures parties de toutes les autres réponses
Je décris des solutions pour divers cas d'utilisation et je traiterai la récursion infinie problème aussi bien
Cas 1: Vous contrôlez les classes , c'est-à-dire que vous écrivez les vôtres
Cat
,Dog
ainsi que lesIAnimal
interface. Vous pouvez simplement suivre la solution fournie par @ marcus-junius-brutus (la réponse la mieux notée)Il n'y aura pas de récursivité infinie s'il existe une interface de base commune comme
IAnimal
Mais que faire si je ne veux pas implémenter l'
IAnimal
interface ou une telle interface?Ensuite, @ marcus-junius-brutus (la réponse la mieux notée) produira une erreur de récurrence infinie. Dans ce cas, nous pouvons faire quelque chose comme ci-dessous.
Nous devrons créer un constructeur de copie à l'intérieur de la classe de base et une sous-classe wrapper comme suit:
.
Et le sérialiseur pour le type
Cat
:Alors, pourquoi un constructeur de copie?
Eh bien, une fois que vous avez défini le constructeur de copie, peu importe combien la classe de base change, votre wrapper continuera avec le même rôle. Deuxièmement, si nous ne définissons pas un constructeur de copie et que nous sous-classons simplement la classe de base, nous devrons alors "parler" en termes de classe étendue, c'est-à-dire,
CatWrapper
. Il est fort possible que vos composants parlent en termes de classe de base et non de type wrapper.Existe-t-il une alternative simple?
Bien sûr, il a maintenant été introduit par Google - voici la
RuntimeTypeAdapterFactory
mise en œuvre:Ici, vous devrez introduire un champ appelé "type" dans
Animal
et la valeur du même à l'intérieurDog
pour être "chien",Cat
pour être "chat"Exemple complet: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html
Cas 2: Vous ne contrôlez pas les classes . Vous rejoignez une entreprise ou utilisez une bibliothèque où les classes sont déjà définies et votre manager ne veut pas que vous les changiez de quelque manière que ce soit - Vous pouvez sous-classer vos classes et leur faire implémenter une interface de marqueurs commune (qui n'a pas de méthode ) comme
AnimalInterface
.Ex:
.
Donc, nous utiliserions
CatWrapper
au lieu deCat
,DogWrapper
au lieuDog
etAlternativeAnimalAdapter
au lieu deIAnimalAdapter
Nous effectuons un test:
Production:
la source
Si vous souhaitez gérer un TypeAdapter pour un type et un autre pour son sous-type, vous pouvez utiliser un TypeAdapterFactory comme ceci:
Cette usine enverra le TypeAdapter le plus précis
la source
Si vous combinez la réponse de Marcus Junius Brutus avec la modification de user2242263, vous pouvez éviter d'avoir à spécifier une grande hiérarchie de classes dans votre adaptateur en définissant votre adaptateur comme travaillant sur un type d'interface. Vous pouvez ensuite fournir des implémentations par défaut de toJSON () et fromJSON () dans votre interface (qui n'inclut que ces deux méthodes) et faire en sorte que chaque classe dont vous avez besoin pour sérialiser implémente votre interface. Pour gérer le cast, dans vos sous-classes, vous pouvez fournir une méthode statique fromJSON () qui désérialise et effectue le cast approprié à partir de votre type d'interface. Cela a fonctionné à merveille pour moi (faites juste attention à la sérialisation / désérialisation des classes qui contiennent des hashmaps - ajoutez ceci lorsque vous instanciez votre générateur gson:
GsonBuilder builder = new GsonBuilder().enableComplexMapKeySerialization();
J'espère que cela aidera quelqu'un à gagner du temps et des efforts!
la source