Question Liste de sérialisation des interfaces GSON


Je suis tombé sur un comportement étrange dans GSON.

Si j'ai la structure de classe suivante:

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

    };
}

Je peux le faire:

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

et obtenir cette sortie:

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

Cependant, si je mets animals dans une classe contenant:

public class Container {

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

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

et appelez:

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

Je reçois:

{"animals":[{}]}

Il semble que GSON puisse sérialiser une liste d'interface List<Interface> quand cette liste est par elle-même, mais quand la liste est contenue dans une autre classe, GSON a des problèmes.

Une idée de ce que je fais mal?

En passant, je peux désérialiser correctement une chaîne json dans le bon type en utilisant un désérialiseur personnalisé. C'est la sérialisation qui me pose des problèmes.

Merci


14
2018-05-10 15:33


origine


Réponses:


C'est loin d'être joli, mais la solution que j'utilise pour le moment est d'utiliser

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

construire un JsonObject. Alors j'appelle:

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

et waa laa, l'objet en forme de json correct.

Points bonus: j'avais une liste de conteneurs imbriqués, je devais donc construire un JsonArray pour pouvoir parcourir mes conteneurs et appeler mes fichiers personnalisés à Json () sur chacun d'eux.

Morale de l'histoire: ajouter des listes d'interface en utilisant

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

tromper et itérer sur une liste de conteneurs utilisant un JsonArray (utiliser juste toJson () dans la liste n'appelle pas votre méthode spéciale sur les conteneurs enfants).

Certainement toujours à la recherche d'une solution plus naturelle.

Heureux codage


5
2018-05-11 18:04



Il suffit simplement d'utiliser un JsonSerializer personnalisé qui récupère explicitement la classe de l'objet (probablement le type déclaré par Gson, pour une raison quelconque). Voici une solution générale pour la sérialisation des interfaces:

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

Pour votre exemple, je peux faire ce qui suit:

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

Cela produit:

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

Le dernier élément est la chaîne correctement sérialisée.

Cela ne suffit pas désérialiser le JSON, malheureusement, car le JSON résultant ne contient aucune information de type. Découvrez cette réponse à Comment sérialiser une classe avec une interface? pour un moyen de suivre le type de l'objet, et donc sérialiser / désérialiser l'objet.


3
2018-03-21 20:40



La dernière version de Gson se comporte toujours comme décrit dans la question initiale. (Je pense que je vais enregistrer une demande d'amélioration si celle-ci n'existe pas déjà.)

Pour ce que ça vaut, Jackson désérialise les deux exemples de structures de données comme prévu. le Container et Animal les classes d'implémentation n'ont besoin que de getters pour le second exemple.

// 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
2018-06-10 22:27