Polymorphisme avec gson

J’ai un problème pour désérialiser une chaîne json avec Gson. Je reçois un tableau de commandes. La commande peut être start, stop, un autre type de commande. Naturellement, j’ai un polymorphism et la commande start / stop hérite de la commande.

Comment puis-je le sérialiser à l’object de commande correct en utilisant gson?

Il semblerait que je ne reçoive que le type de base, c’est-à-dire le type déclaré et jamais le type d’exécution.

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 de l’utilisation de gson-2.0, vous ne voulez vraiment pas utiliser la méthode registerTypeHierarchyAdapter , mais plutôt la méthode registerTypeAdapter plus banale. Et vous n’avez certainement pas besoin de faire des instances ou des adaptateurs d’écriture 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. En tout cas, voici le code (package et importations supprimés) (également disponible dans github ):

La classe de base (interface dans mon cas):

public interface IAnimal { public Ssortingng sound(); } 

Les deux classes dérivées, Cat:

 public class Cat implements IAnimal { public Ssortingng name; public Cat(Ssortingng name) { super(); this.name = name; } @Override public Ssortingng sound() { return name + " : \"meaow\""; }; } 

Et chien:

 public class Dog implements IAnimal { public Ssortingng name; public int ferocity; public Dog(Ssortingng name, int ferocity) { super(); this.name = name; this.ferocity = ferocity; } @Override public Ssortingng sound() { return name + " : \"bark\" (ferocity level:" + ferocity + ")"; } } 

Le IAnimalAdapter:

 public class IAnimalAdapter implements JsonSerializer, JsonDeserializer{ private static final Ssortingng CLASSNAME = "CLASSNAME"; private static final Ssortingng INSTANCE = "INSTANCE"; @Override public JsonElement serialize(IAnimal src, Type typeOfSrc, JsonSerializationContext context) { JsonObject retValue = new JsonObject(); Ssortingng className = src.getClass().getName(); retValue.addProperty(CLASSNAME, className); JsonElement elem = context.serialize(src); retValue.add(INSTANCE, elem); return retValue; } @Override public IAnimal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME); Ssortingng className = prim.getAsSsortingng(); Class klass = null; try { klass = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new JsonParseException(e.getMessage()); } return context.deserialize(jsonObject.get(INSTANCE), klass); } } 

Et la classe de test:

 public class Test { public static void main(Ssortingng[] args) { IAnimal animals[] = new IAnimal[]{new Cat("Kitty"), new Dog("Brutus", 5)}; Gson gsonExt = null; { GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(IAnimal.class, new IAnimalAdapter()); gsonExt = builder.create(); } for (IAnimal animal : animals) { Ssortingng animalJson = gsonExt.toJson(animal, IAnimal.class); System.out.println("serialized with the custom serializer:" + animalJson); IAnimal animal2 = gsonExt.fromJson(animalJson, IAnimal.class); System.out.println(animal2.sound()); } } } 

Lorsque vous exécutez le test :: main, vous obtenez la sortie suivante:

 serialized with the custom serializer: {"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Cat","INSTANCE":{"name":"Kitty"}} Kitty : "meaow" serialized with the custom serializer: {"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Dog","INSTANCE":{"name":"Brutus","ferocity":5}} Brutus : "bark" (ferocity level:5) 

J’ai déjà fait la même chose 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 complique le maintien à chaque fois que vous voulez append un autre champ à Dog ou Cat.

Gson a actuellement un mécanisme pour enregistrer un adaptateur de hiérarchie de types qui pourrait être configuré pour une désérialisation polymorphe simple, mais je ne vois pas comment cela se produit, car un adaptateur de hiérarchie de types semble être un créateur de sérialiseur / désérialiseur / instance combiné, en laissant les détails de la création de l’instance au codeur, sans fournir aucun enregistrement de type polymorphe réel.

Il semble que Gson aura bientôt le RuntimeTypeAdapter pour simplifier la désérialisation polymorphe. 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, alors je pense que vous devrez déployer votre propre solution, en enregistrant un désérialiseur personnalisé en tant qu’adaptateur de hiérarchie de types ou en tant que Type Adapter. Voici un exemple.

 // output: // Starting machine1 // Stopping machine2 import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; public class Foo { // [{"machine_name":"machine1","command":"start"},{"machine_name":"machine2","command":"stop"}] static Ssortingng jsonInput = "[{\"machine_name\":\"machine1\",\"command\":\"start\"},{\"machine_name\":\"machine2\",\"command\":\"stop\"}]"; public static void main(Ssortingng[] args) { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); CommandDeserializer deserializer = new CommandDeserializer("command"); deserializer.registerCommand("start", Start.class); deserializer.registerCommand("stop", Stop.class); gsonBuilder.registerTypeAdapter(Command.class, deserializer); Gson gson = gsonBuilder.create(); Command[] commands = gson.fromJson(jsonInput, Command[].class); for (Command command : commands) { command.execute(); } } } class CommandDeserializer implements JsonDeserializer { Ssortingng commandElementName; Gson gson; Map> commandRegistry; CommandDeserializer(Ssortingng commandElementName) { this.commandElementName = commandElementName; GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); gson = gsonBuilder.create(); commandRegistry = new HashMap>(); } void registerCommand(Ssortingng command, Class commandInstanceClass) { commandRegistry.put(command, commandInstanceClass); } @Override public Command deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { try { JsonObject commandObject = json.getAsJsonObject(); JsonElement commandTypeElement = commandObject.get(commandElementName); Class commandInstanceClass = commandRegistry.get(commandTypeElement.getAsSsortingng()); Command command = gson.fromJson(json, commandInstanceClass); return command; } catch (Exception e) { throw new RuntimeException(e); } } } abstract class Command { Ssortingng machineName; Command(Ssortingng machineName) { this.machineName = machineName; } abstract void execute(); } class Stop extends Command { Stop(Ssortingng machineName) { super(machineName); } void execute() { System.out.println("Stopping " + machineName); } } class Start extends Command { Start(Ssortingng machineName) { super(machineName); } void execute() { System.out.println("Starting " + machineName); } } 

GSON a un bon exemple 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:

  gson = new GsonBuilder() .registerTypeAdapter(BaseQuestion.class, new BaseQuestionAdaptor()) .create(); 

La méthode de sérialisation de l’adaptateur peut être une vérification en cascade du type qu’il sérialise.

  JsonElement result = new JsonObject(); if (src instanceof SliderQuestion) { result = context.serialize(src, SliderQuestion.class); } else if (src instanceof TextQuestion) { result = context.serialize(src, TextQuestion.class); } else if (src instanceof ChoiceQuestion) { result = context.serialize(src, ChoiceQuestion.class); } return result; 

La désérialisation est un peu piratée. Dans l’exemple de test unitaire, il vérifie l’existence d’atsortingbuts révélateurs pour décider de la classe à désérialiser. Si vous pouvez changer la source de l’object que vous sérialisez, vous pouvez append un atsortingbut «classType» à chaque instance contenant le nom FQN du nom de la classe d’instance. C’est tellement très peu orienté object.

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 fonctionner avec tous les types d’objects (pas seulement IAnimal) avec les modifications suivantes:

 class InheritanceAdapter implements JsonSerializer, JsonDeserializer { .... public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) .... public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException .... } 

Et dans la classe de test:

 public class Test { public static void main(Ssortingng[] args) { .... builder.registerTypeAdapter(IAnimal.class, new InheritanceAdapter()); .... } 

Longtemps a passé, mais je ne pouvais pas trouver une très bonne solution en ligne .. Voici une petite torsion sur la solution de @ MarcusJuniusBrutus, qui évite la récursion infinie.

Conservez le même désérialiseur, mais enlevez le sérialiseur –

 public class IAnimalAdapter implements JsonDeSerializer { private static final Ssortingng CLASSNAME = "CLASSNAME"; private static final Ssortingng INSTANCE = "INSTANCE"; @Override public IAnimal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME); Ssortingng className = prim.getAsSsortingng(); Class klass = null; try { klass = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new JsonParseException(e.getMessage()); } return context.deserialize(jsonObject.get(INSTANCE), klass); } } 

Ensuite, dans votre classe d’origine, ajoutez un champ avec @SerializedName("CLASSNAME") . L’astuce consiste maintenant à l’initialiser dans le constructeur de la classe de base , alors faites de votre interface une classe abstraite.

 public abstract class IAnimal { @SerializedName("CLASSNAME") public Ssortingng className; public IAnimal(...) { ... className = this.getClass().getName(); } } 

La raison pour laquelle il n’ya pas de récursion infinie ici est que nous passons la classe d’exécution réelle (c.-à-d. Dog not IAnimal) à context.deserialize. Cela n’appellera pas notre adaptateur de type, tant que nous utilisons registerTypeAdapter et non registerTypeHierarchyAdapter

Google a publié son propre RuntimeTypeAdapterFactory pour gérer le polymorphism mais, malheureusement, il ne fait pas partie du kernel gson (vous devez copier et coller la classe dans votre projet).

Exemple:

 RuntimeTypeAdapterFactory runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory .of(Animal.class, "type") .registerSubtype(Dog.class, "dog") .registerSubtype(Cat.class, "cat"); Gson gson = new GsonBuilder() .registerTypeAdapterFactory(runtimeTypeAdapterFactory) .create(); 

Ici, j’ai posté un exemple complet de travail avec le modèle Animal, Dog and Cat.

Je pense qu’il est préférable de compter sur cet adaptateur plutôt que de le réimplémenter à partir de zéro.

Si vous voulez gérer un TypeAdapter pour un type et un autre pour son sous-type, vous pouvez utiliser un TypeAdapterFactory comme ceci:

 public class InheritanceTypeAdapterFactory implements TypeAdapterFactory { private Map, TypeAdapter> adapters = new LinkedHashMap<>(); { adapters.put(Animal.class, new AnimalTypeAdapter()); adapters.put(Dog.class, new DogTypeAdapter()); } @SuppressWarnings("unchecked") @Override public  TypeAdapter create(Gson gson, TypeToken typeToken) { TypeAdapter typeAdapter = null; Class currentType = Object.class; for (Class type : adapters.keySet()) { if (type.isAssignableFrom(typeToken.getRawType())) { if (currentType.isAssignableFrom(type)) { currentType = type; typeAdapter = (TypeAdapter)adapters.get(type); } } } return typeAdapter; } } 

Cette usine enverra le TypeAdapter le plus précis