ti-enxame.com

Desserializando uma classe abstrata no Gson

Eu tenho um objeto de árvore no formato JSON que estou tentando desserializar com o Gson. Cada nó contém seus nós filhos como campos do tipo de objeto Node. Node é uma interface que possui várias implementações de classes concretas. Durante o processo de desserialização, como posso comunicar ao Gson qual classe concreta implementar ao desserializar o nó, se eu não souber a priori a que tipo o nó pertence? Cada nó tem um campo de membro especificando o tipo. Existe uma maneira de acessar o campo quando o objeto está em forma serializada e, de alguma forma, comunicar o tipo ao Gson?

Obrigado!

34
user437480

Sugiro adicionar um JsonDeserializer personalizado para Nodes:

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Node.class, new NodeDeserializer())
    .create();

Você poderá acessar o JsonElement representando o nó no método do deserializer, convertê-lo em um JsonObject e recuperar o campo que especifica o tipo. Você pode criar uma instância do tipo correto de Node com base nisso.

42
ColinD

Você precisará registrar tanto o JSONSerializer quanto o JSONDeserializer. Além disso, você pode implementar um adaptador genérico para todas as suas interfaces da seguinte maneira:

  • Durante a serialização: Adicione uma informação META do tipo de classe real impl.
  • Durante o DeSerialization: Recupere essa meta info e chame o JSONDeserailize dessa classe

Aqui está a implementação que eu usei para mim e funciona bem.

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;
    }

}

Então você pode registrar este adaptador para todas as suas interfaces da seguinte forma

Gson gson = new GsonBuilder()
        .registerTypeAdapter(IInterfaceOne.class,
                new PropertyBasedInterfaceMarshal())
        .registerTypeAdapter(IInterfaceTwo.class,
                new PropertyBasedInterfaceMarshal()).create();
25
Guruprasad GV

Tanto quanto eu posso dizer isso não funciona para os tipos de não-coleção, ou mais especificamente, situações em que o tipo concreto é usado para serializar e o tipo de interface é usado para desserializar. Ou seja, se você tiver uma classe simples implementando uma interface e serializar a classe concreta, especifique a interface para desserializar e terminará em uma situação irrecuperável.

No exemplo acima, o adaptador de tipo é registrado na interface, mas quando você serializa usando a classe concreta, ele não será usado, o que significa que os dados de CLASS_META_KEY nunca serão definidos.

Se você especificar o adaptador como um adaptador hierárquico (assim dizendo ao gson para usá-lo para todos os tipos na hierarquia), você terminará em um loop infinito, pois o serializador continuará se chamando.

Alguém sabe como serializar a partir de uma implementação concreta de uma interface e, em seguida, desserializar usando apenas a interface e um InstanceCreator?

Por padrão, parece que o gson irá criar a instância concreta, mas não define seus campos. 

O problema está registrado aqui:

http://code.google.com/p/google-gson/issues/detail?id=411&q=interface

1
Jason Polites

Você tem que usar a classe TypeToken do Google Gson. Você vai precisar, é claro, de uma classe genérica T para fazer funcionar

Type fooType = new TypeToken<Foo<Bar>>() {}.getType();

gson.toJson (foo, fooType);

gson.fromJson(json, fooType);
0
paul