2011-05-10 5 views
13

Natknąłem się na dziwne zachowanie w GSON.Serializing Lista interfejsów GSON

Jeśli mam następujące klasy strukturę:

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 

    }; 
} 

mogę to zrobić:

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

i uzyskać ten wynik:

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

Jednakże, jeśli mogę umieścić animals do klasa zawierająca:

public class Container { 

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

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

i zadzwonić:

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

uzyskać:

{"animals":[{}]} 

Wygląda GSON można szeregować listę interfejs List<Interface> kiedy ta lista jest samo w sobie, ale gdy lista jest zawarta w innej klasie GSON ma problemy.

Każdy pomysł, co robię źle?

Na marginesie mogę poprawnie deserializować ciąg jsonów do poprawnego typu przy użyciu niestandardowego deserializera. To serializacja daje mi problemy.

Dzięki

+0

Zawsze można uzyskać kod źródłowy Gsona i zobaczyć, co faktycznie robi w tej metodzie. –

Odpowiedz

5

To dość daleko, ale rozwiązanie używam do teraz jest użycie

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

zbudować JSONObject. Następnie zadzwonię:

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

i waa laa, obiekt w poprawnej formie json.

Dodatkowe punkty: Miałem listę zagnieżdżonych kontenerów, więc musiałem skonstruować JsonArray, aby móc wykonywać iteracje nad kontenerami i wywoływać moje niestandardowe ustawienia w Json() na każdym z nich.

Morał z tej historii: Dodawanie list interfejs z

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

sztuczki i iteracyjne nad listą pojemników z wykorzystaniem JsonArray (tylko przy użyciu toJSON() na liście nie nazywają swoją specjalną metodę na dzieci pojemniki).

Zdecydowanie wciąż poszukuje bardziej naturalnego rozwiązania.

Szczęśliwy kodowania

+0

Niestety nie działa to w przypadku list Scala. – onejigtwojig

0

Najnowsze wydanie Gson nadal zachowuje się jak opisano w pierwotnym pytaniu. (Myślę, że zarejestruję żądanie rozszerzenia, jeśli jeszcze nie istnieje.)

W przypadku, gdy jest to warte, Jackson przekształca obie przykładowe struktury danych zgodnie z oczekiwaniami. Klasa implementująca Container i potrzebuje tylko modułów pobierających dla drugiego przykładu.

// 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

Dzięki za wskazówkę dla Jacksona. Następnym razem, gdy gram z jsonem, przyjrzę się temu! –

+1

Jest to znany problem z Gson. Wydanie 170 zostało pierwotnie zarejestrowane w październiku 2009 r. W związku z tym. W związku z implementacją do wydania 231 podobno planowaną do dołączenia do następnej wersji, problem ten może wkrótce zostać rozwiązany. –

+3

Niestety, to 2014 i [numer 231] (https://code.google.com/p/google-gson/issues/detail?id=231) pozostaje nierozwiązany :( – dimo414

3

To wystarczy po prostu użyć niestandardowego JsonSerializer jawnie dostaje klasę obiektu (przypuszczalnie Gson coraz deklarowany typ zamiast z jakiegoś powodu). Oto ogólne rozwiązanie dla identyfikatorów seryjnych Interfejsy:

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

Dla przykładu, można wykonać następujące:

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

This wyjścia:

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

Ostatnia pozycja będąc prawidłowo odcinkach ciąg .

To nie jest wystarczające, aby deserializować JSON, niestety, ponieważ wynikowy JSON nie zawiera żadnych informacji o typie. Sprawdź tę odpowiedź na How to serialize a class with an interface?, aby dowiedzieć się, jak śledzić typ obiektu, a następnie serializować/deserializować obiekt.