2011-05-10 12 views
13

Me encontré con un comportamiento extraño en GSON.Lista de interfaces en serie GSON

Si tengo la siguiente estructura de clases:

public interface Animal { 
    public void nothing(); 
} 

public class Cat implements Animal { 
    private String name; 

    public Cat(String name) { 
     super(); 
     this.name = name; 
    } 

    public Cat(){} 

    @Override 
     public void nothing() { 
     // TODO Auto-generated method stub 
     }; 
    } 

public class Dog implements Animal { 
    private String name; 

    public Dog(String name) { 
      super(); 
     this.name = name; 
    } 

    public Dog(){} 

    @Override 
    public void nothing() { 
     // TODO Auto-generated method stub 

    }; 
} 

que pueda hacer esto:

ArrayList<Animal> animals = new ArrayList<Animal>(); 
    animals.add(new Cat("Betty")); 
    animals.add(new Dog("Fred")); 
    System.out.println(gson.toJson(animals)); 

y obtener este resultado:

[{"name":"Betty"},{"name":"Fred"}] 

Sin embargo, si pongo animals en una clase contenedora:

public class Container { 

List<Animal> animals = new ArrayList<Animal>(); 

public void addAnimal(Animal a){ 
    animals.add(a); 
} 
} 

y llame:

Container container = new Container(); 
container.addAnimal(new Cat("betty")); 
System.out.println(gson.toJson(container)); 

me sale:

{"animals":[{}]} 

Parece que GSON puede serializar una lista de una interfaz List<Interface> cuando esa lista no es en sí mismo, pero cuando la lista está contenido en otra clase, GSON tiene problemas.

¿Alguna idea de lo que estoy haciendo mal?

Como nota al margen, puedo deserializar correctamente una cadena json en el tipo correcto utilizando un deserializador personalizado. Es la serialización que me está dando problemas.

Gracias

+0

Siempre puede obtener el código fuente de Gson y ver lo que realmente están haciendo en ese método. –

Respuesta

5

Está lejos de ser bonita, pero la solución que estoy usando por ahora es utilizar

JsonObject jsonObject = gson.toJsonTree(container).getAsJsonObject(); 

para construir un JSONObject. Entonces llamo:

jsonObject.remove("animals"); 
jsonObject.add("animals",gson.toJsonTree(container.getAnimals())); 

y waa laa, el objeto en forma json correcta.

Puntos de bonificación: Tenía una lista de contenedores anidados, así que tuve que construir un JsonArray para poder iterar sobre mis contenedores y llamar a mi personalizado a Json() en cada uno.

Moraleja de la historia: Añadir Listas de interfaz usando el truco

jsonObject.remove(); 
jsonObject.add(propertyName, property); 

y iterar sobre una lista de contenedores utilizando un JSONArray (sólo usar toJson() en la lista de no llamar a su método especial en los niños contenedores).

Definitivamente sigue buscando una solución más natural.

feliz de codificación

+0

esto no parece funcionar para Scala Lists lamentablemente. – onejigtwojig

0

La última versión de Gson todavía se comporta como se describe en la pregunta original. (Creo que registraré una solicitud de mejora si aún no existe).

Por lo que vale, Jackson deserializa ambas estructuras de datos de ejemplo como se esperaba. Las clases de implementación Container y Animal solo necesitan getters para el segundo ejemplo.

// output: {"animals":[{"name":"betty"},{"name":"fred"}]} 

import java.io.StringWriter; 
import java.util.ArrayList; 

import org.codehaus.jackson.map.ObjectMapper; 

public class JacksonFoo 
{ 
    public static void main(String[] args) throws Exception 
    { 
    Container container = new Container(); 
    container.addAnimal(new Cat("betty")); 
    container.addAnimal(new Dog("fred")); 
    ObjectMapper mapper = new ObjectMapper(); 
    StringWriter writer = new StringWriter(); 
    mapper.writeValue(writer, container); 
    System.out.println(writer); 
    } 
} 

class Container 
{ 
    ArrayList<Animal> animals = new ArrayList<Animal>(); 

    public void addAnimal(Animal a) 
    { 
    animals.add(a); 
    } 

    public ArrayList<Animal> getAnimals() 
    { 
    return animals; 
    } 
} 

interface Animal 
{ 
    public void nothing(); 
} 

class Cat implements Animal 
{ 
    private String name; 

    public Cat(String name) 
    { 
    this.name = name; 
    } 

    public Cat() 
    { 
    } 

    @Override 
    public void nothing() 
    { 
    } 

    public String getName() 
    { 
    return name; 
    } 
} 

class Dog implements Animal 
{ 
    private String name; 

    public Dog(String name) 
    { 
    this.name = name; 
    } 

    public Dog() 
    { 
    } 

    @Override 
    public void nothing() 
    { 
    } 

    public String getName() 
    { 
    return name; 
    } 
} 
+0

Gracias por el puntero a Jackson. ¡La próxima vez que juegue con json lo echaré un vistazo! –

+1

Este es un problema conocido con Gson. El problema 170 se inició originalmente en octubre de 2009, al respecto. Con una implementación para el problema 231 que supuestamente se planea incluir con el próximo lanzamiento, este problema puede resolverse pronto. –

+3

Por desgracia, es 2014 y [Número 231] (https://code.google.com/p/google-gson/issues/detail?id=231) sigue sin resolverse :( – dimo414

3

Es suficiente para utilizar simplemente una JsonSerializer personalizada que recibe de manera explícita la clase del objeto (presumiblemente Gson de conseguir el tipo declarado en su lugar, por alguna razón). Aquí hay una solución general para interfaces de serialización:

public static class InterfaceSerializer<T> implements JsonSerializer<T> { 
    public JsonElement serialize(T link, Type type, 
           JsonSerializationContext context) { 
     // Odd Gson quirk 
     // not smart enough to use the actual type rather than the interface 
     return context.serialize(link, link.getClass()); 
    } 
} 

Para su ejemplo, puede hacer lo siguiente:

GsonBuilder gbuild = new GsonBuilder(); 
Gson standard = gbuild.create(); 

ArrayList<Animal> animals = Lists.newArrayList(new Cat("Betty"),new Dog("Fred")); 
System.out.println(standard.toJson(animals)); 

Container c = new Container(); 
c.animals.addAll(animals); 
System.out.println(standard.toJson(c)); 

Gson interfaceAware = gbuild 
    .registerTypeAdapter(Animal.class, new InterfaceSerializer<>()).create(); 
System.out.println(interfaceAware.toJson(c)); 

Este salidas:

[{"name":"Betty","hates":"Everything"},{"name":"Fred","loves":"Everything"}] 
{"animals":[{},{}]} 
{"animals":[{"name":"Betty","hates":"Everything"}, "name":"Fred","loves":"Everything"}]} 

El último elemento es la secuencia correcta serializado .

Esto no es suficiente para deserializar JSON, desafortunadamente, porque el JSON resultante no contiene ningún tipo de información. Consulte esta respuesta en How to serialize a class with an interface? para conocer el tipo de objeto y, por lo tanto, serializar/deserializar el objeto.