Désérialiser une classe abstraite dans GSON
-
26-09-2019 - |
Question
J'ai un objet d'arbre au format JSON que j'essaie de désérialiser avec GSON. Chaque nœud contient ses nœuds enfants comme champs de nœud de type objet. Le nœud est une interface, qui a plusieurs implémentations de classe concrètes. Pendant le processus de désérialisation, comment puis-je communiquer à GSON quelle classe concrète à implémenter lors de la désérialisation du nœud, si je ne sais pas a priori à quel type le nœud appartient? Chaque nœud a un champ membre spécifiant le type. Existe-t-il un moyen d'accéder au champ lorsque l'objet est sous forme sérialisée et de communiquer en quelque sorte le type à GSON?
Merci!
La solution
Je suggère d'ajouter une coutume JSONSeRializer pour Node
S:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Node.class, new NodeDeserializer())
.create();
Vous pourrez accéder au JsonElement
Représentant le nœud dans la méthode du désérialiseur, convertissez-le en un JsonObject
, et récupérer le champ qui spécifie le type. Vous pouvez ensuite créer une instance du bon type de Node
basé sur cela.
Autres conseils
Vous devrez enregistrer JSonSerializer et JSondeSerializer. Vous pouvez également implémenter un adaptateur générique pour toutes vos interfaces de la manière suivante:
- Pendant la sérialisation: ajoutez un méta-info du type de classe IMP réel.
- Pendant la désérialisation: récupèrez les méta-informations et appelez le jsonseserailize de cette classe
Voici l'implémentation que j'ai utilisée pour moi et fonctionne bien.
public class PropertyBasedInterfaceMarshal implements
JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_META_KEY = "CLASS_META_KEY";
@Override
public Object deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject jsonObj = jsonElement.getAsJsonObject();
String className = jsonObj.get(CLASS_META_KEY).getAsString();
try {
Class<?> clz = Class.forName(className);
return jsonDeserializationContext.deserialize(jsonElement, clz);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
@Override
public JsonElement serialize(Object object, Type type,
JsonSerializationContext jsonSerializationContext) {
JsonElement jsonEle = jsonSerializationContext.serialize(object, object.getClass());
jsonEle.getAsJsonObject().addProperty(CLASS_META_KEY,
object.getClass().getCanonicalName());
return jsonEle;
}
}
Ensuite, vous pouvez enregistrer cet adaptateur pour toutes vos interfaces comme suit
Gson gson = new GsonBuilder()
.registerTypeAdapter(IInterfaceOne.class,
new PropertyBasedInterfaceMarshal())
.registerTypeAdapter(IInterfaceTwo.class,
new PropertyBasedInterfaceMarshal()).create();
Pour autant que je sache, cela ne fonctionne pas pour les types de non-collection, ou plus précisément, les situations où le type de béton est utilisé pour sérialiser, et le type d'interface est utilisé pour désérialiser. Autrement dit, si vous avez une classe simple implémentant une interface et que vous sérialisez la classe concrète, puis spécifiez l'interface pour désérialiser, vous vous retrouverez dans une situation irréprochable.
Dans l'exemple ci-dessus, l'adaptateur de type est enregistré par rapport à l'interface, mais lorsque vous sérialisez à l'aide de la classe de béton, il ne sera pas utilisé, ce qui signifie que les données class_meta_key ne seront jamais définies.
Si vous spécifiez l'adaptateur comme un adaptateur hiérarchique (disant ainsi à GSON de l'utiliser pour tous les types dans la hiérarchie), vous vous retrouverez dans une boucle infinie car le sérialiseur continuera de s'appeler.
Quelqu'un sait-il comment sérialiser à partir d'une implémentation concrète d'une interface, puis désérialiser en utilisant uniquement l'interface et un instanCreator?
Par défaut, il semble que GSON crée l'instance en béton, mais ne définit pas ses champs.
Le problème est enregistré ici:
http://code.google.com/p/google-gon/issues/detail?id=411&q=interface
Vous devez utiliser TypeToken
Classe de Google GSON. Vous aurez bien sûr besoin d'avoir une classe T générique pour le faire fonctionner
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.tojson (foo, joueur de pied);
gson.fromJson(json, fooType);