Eu tenho um problema ao desserializar uma string json com Gson. Eu recebo uma série de comandos. O comando pode ser iniciar, parar ou algum outro tipo de comando. Naturalmente, tenho polimorfismo, e o comando iniciar / parar herda do comando.
Como posso serializá-lo de volta para o objeto de comando correto usando gson?
Parece que obtenho apenas o tipo base, que é o tipo declarado e nunca o tipo de tempo de execução.
java
json
polymorphism
gson
deserialization
Sophie
fonte
fonte
Respostas:
É um pouco tarde, mas tive que fazer exatamente a mesma coisa hoje. Então, com base em minha pesquisa e ao usar gson-2.0, você realmente não quer usar o método registerTypeHierarchyAdapter , mas o mais mundano registerTypeAdapter . E você certamente não precisa fazer instanceofs ou escrever adaptadores para as classes derivadas: apenas um adaptador para a classe base ou interface, desde que você esteja satisfeito com a serialização padrão das classes derivadas. De qualquer forma, aqui está o código (pacote e importações removidos) (também disponível no github ):
A classe base (interface no meu caso):
As duas classes derivadas, Cat:
E Cachorro:
O IAnimalAdapter:
E a classe de teste:
Ao executar o Test :: main, você obtém a seguinte saída:
Na verdade, fiz o acima usando o método registerTypeHierarchyAdapter também, mas isso parecia exigir a implementação de classes serializador / desserializador DogAdapter e CatAdapter, cuja manutenção é difícil sempre que você quiser adicionar outro campo a Dog ou Cat.
fonte
Gson atualmente tem um mecanismo para registrar um adaptador de hierarquia de tipo que supostamente pode ser configurado para desserialização polimórfica simples, mas não vejo como é o caso, já que um adaptador de hierarquia de tipo parece ser apenas um serializador / desserializador / criador de instância combinado, deixando os detalhes da criação da instância para o codificador, sem fornecer nenhum registro de tipo polimórfico real.
Parece que em breve o Gson terá a
RuntimeTypeAdapter
desserialização polimórfica mais simples. Consulte http://code.google.com/p/google-gson/issues/detail?id=231 para obter mais informações.Se o uso do novo
RuntimeTypeAdapter
não for possível e você tiver que usar Gson, então acho que você terá que lançar sua própria solução, registrando um desserializador customizado como um Type Hierarchy Adapter ou como Type Adapter. A seguir está um exemplo.fonte
RuntimeTypeAdapter
agora está completo, infelizmente ainda não parece estar no núcleo Gson. :-(Marcus Junius Brutus teve uma ótima resposta (obrigado!). Para estender seu exemplo, você pode tornar sua classe de adaptador genérica para funcionar com todos os tipos de objetos (não apenas IAnimal) com as seguintes alterações:
E na classe de teste:
fonte
GSON tem um caso de teste muito bom aqui, mostrando como definir e registrar um adaptador de hierarquia de tipo.
http://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java?r=739
Para usar isso, faça o seguinte:
O método de serialização do adaptador pode ser uma verificação if-else em cascata de que tipo ele está serializando.
A desserialização é um pouco hackeada. No exemplo de teste de unidade, ele verifica a existência de atributos indicadores para decidir para qual classe será desserializado. Se você pode alterar a fonte do objeto que está serializando, pode adicionar um atributo 'classType' a cada instância que contém o FQN do nome da classe de instância. Porém, isso é muito não orientado a objetos.
fonte
O Google lançou seu próprio RuntimeTypeAdapterFactory para lidar com o polimorfismo, mas infelizmente ele não faz parte do núcleo gson (você deve copiar e colar a classe dentro do seu projeto).
Exemplo:
Aqui eu postei um exemplo completo de trabalho usando os modelos Animal, Dog e Cat.
Eu acho que é melhor confiar neste adaptador em vez de reimplementá-lo do zero.
fonte
Muito tempo se passou, mas não consegui encontrar uma solução realmente boa online .. Aqui está uma pequena variação na solução de @MarcusJuniusBrutus, que evita a recursão infinita.
Mantenha o mesmo desserializador, mas remova o serializador -
Então, em sua classe original, adicione um campo com
@SerializedName("CLASSNAME")
. O truque agora é inicializar isso no construtor da classe base , portanto, transforme sua interface em uma classe abstrata.A razão de não haver recursão infinita aqui é que passamos a classe de tempo de execução real (ou seja, Dog não IAnimal) para
context.deserialize
. Isso não chamará nosso adaptador de tipo, contanto que usemosregisterTypeAdapter
e nãoregisterTypeHierarchyAdapter
fonte
Resposta atualizada - melhores partes de todas as outras respostas
Estou descrevendo soluções para vários casos de uso e também abordaria o problema de recursão infinita
Caso 1: Você está no controle das classes , ou seja, você começa a escrever seus próprios
Cat
,Dog
classes, bem como aIAnimal
interface. Você pode simplesmente seguir a solução fornecida por @ marcus-junius-brutus (a resposta com melhor classificação)Não haverá recursão infinita se houver uma interface de base comum como
IAnimal
Mas, e se eu não quiser implementar a
IAnimal
ou qualquer outra interface?Então, @ marcus-junius-brutus (a resposta mais bem avaliada) produzirá um erro de recursão infinito. Nesse caso, podemos fazer algo como a seguir.
Teríamos que criar um construtor de cópia dentro da classe base e uma subclasse de wrapper da seguinte maneira:
.
E o serializador para o tipo
Cat
:Então, por que um construtor de cópia?
Bem, uma vez que você defina o construtor de cópia, não importa o quanto a classe base mude, seu wrapper continuará com a mesma função. Em segundo lugar, se não definirmos um construtor de cópia e simplesmente criarmos uma subclasse da classe base, teremos que "conversar" em termos da classe estendida, ou seja
CatWrapper
,. É bem possível que seus componentes falem em termos da classe base e não do tipo de invólucro.Existe uma alternativa fácil?
Claro, agora foi introduzido pelo Google - esta é a
RuntimeTypeAdapterFactory
implementação:Aqui, você precisaria inserir um campo chamado "digite"
Animal
e o valor do mesmo dentroDog
de ser "cachorro",Cat
ser "gato"Exemplo completo: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html
Caso 2: Você não está no controle das aulas . Você entra para uma empresa ou usa uma biblioteca onde as classes já estão definidas e seu gerente não quer que você as altere de nenhuma forma - Você pode criar uma subclasse de suas classes e fazer com que implementem uma interface de marcador comum (que não tem métodos ) como
AnimalInterface
.Ex:
.
Então, estaríamos usando em
CatWrapper
vez deCat
, emDogWrapper
vez deDog
e emAlternativeAnimalAdapter
vez deIAnimalAdapter
Realizamos um teste:
Resultado:
fonte
Se quiser gerenciar um TypeAdapter para um tipo e outro para seu subtipo, você pode usar um TypeAdapterFactory como este:
Esta fábrica enviará o TypeAdapter mais preciso
fonte
Se você combinar a resposta de Marcus Junius Brutus com a edição do usuário 2242263, poderá evitar a necessidade de especificar uma grande hierarquia de classes em seu adaptador, definindo seu adaptador como trabalhando em um tipo de interface. Você pode então fornecer implementações padrão de toJSON () e fromJSON () em sua interface (que inclui apenas esses dois métodos) e ter todas as classes necessárias para serializar implementar sua interface. Para lidar com a conversão, em suas subclasses você pode fornecer um método estático fromJSON () que desserializa e executa a conversão apropriada do seu tipo de interface. Isso funcionou muito bem para mim (apenas tome cuidado ao serializar / desserializar classes que contêm hashmaps - adicione isso ao instanciar seu construtor gson:
GsonBuilder builder = new GsonBuilder().enableComplexMapKeySerialization();
Espero que isso ajude alguém a economizar tempo e esforço!
fonte