J'ai un objet arbre au format JSON que j'essaie de désérialiser avec Gson. Chaque nœud contient ses nœuds enfants sous forme de champs de type d'objet Node. Node est une interface qui a plusieurs implémentations de classes concrètes. Pendant le processus de désérialisation, comment puis-je indiquer à Gson quelle classe concrète mettre en œuvre lors de la désérialisation du noeud, si je ne connais pas à priori le type auquel appartient le noeud? 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 le type à Gson?
Merci!
Je suggère d'ajouter un JsonDeserializer personnalisé pour Node
s:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Node.class, new NodeDeserializer())
.create();
Vous pourrez accéder à la variable JsonElement
représentant le nœud dans la méthode du désérialiseur, la convertir en une variable JsonObject
et extraire le champ spécifiant le type. Vous pouvez ensuite créer une instance du type correct de Node
en fonction de cela.
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:
Voici l'implémentation que j'ai utilisée pour moi-même 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();
Autant que je sache, cela ne fonctionne pas pour les types sans collection, ou plus précisément pour les situations où le type concret est utilisé pour la sérialisation et le type d'interface pour la désérialisation. 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 à désérialiser, vous vous retrouverez dans une situation irrécupérable.
Dans l'exemple ci-dessus, l'adaptateur de type est enregistré sur l'interface, mais lorsque vous sérialisez à l'aide de la classe concrète, 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 en tant qu'adaptateur hiérarchique (indiquant ainsi à gson de l'utiliser pour tous les types de la hiérarchie), vous vous retrouverez dans une boucle infinie car le sérialiseur continuera à s'appeler lui-même.
Quelqu'un sait comment sérialiser à partir d'une implémentation concrète d'une interface, puis désérialiser en utilisant uniquement l'interface et un InstanceCreator?
Par défaut, il semble que gson crée l'instance concrète, mais ne définit pas ses champs.
Le problème est enregistré ici:
http://code.google.com/p/google-gson/issues/detail?id=411&q=interface
Vous devez utiliser la classe TypeToken
de Google Gson. Vous aurez bien sûr besoin d’une classe générique T pour que cela fonctionne
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson (foo, fooType);
gson.fromJson(json, fooType);