web-dev-qa-db-fra.com

Désérialiser une classe abstraite en Gson

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!

34
user437480

Je suggère d'ajouter un JsonDeserializer personnalisé pour Nodes:

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.

42
ColinD

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 une information META du type de classe impl réel.
  • Pendant la désérialisation: récupérez cette méta-information et appelez le JSONDeserailize de cette classe

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();
25
Guruprasad GV

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

1
Jason Polites

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