Подтвердить что ты не робот

Сериализованный список интерфейсов GSON

Я столкнулся с каким-то странным поведением в GSON.

Если у меня есть следующая структура класса:

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

    };
}

Я могу это сделать:

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

и получить этот вывод:

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

Однако, если я помещаю animals в содержащий класс:

public class Container {

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

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

и вызовите:

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

Я получаю:

{"animals":[{}]}

Похоже, что GSON может сериализовать список интерфейса List<Interface>, когда этот список сам по себе, но когда список содержится в другом классе, у GSON есть проблемы.

Любая идея, что я делаю неправильно?

Как замечание, я могу правильно десериализовать строку json в правильный тип, используя собственный десериализатор. Это сериализация, которая дает мне проблемы.

Спасибо

4b9b3361

Ответ 1

Это далеко не красиво, но решение, которое я использую сейчас, это использовать

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

для создания JsonObject. Затем я вызываю:

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

и waa laa, объект в правильной форме json.

Бонусные баллы: у меня был список вложенных контейнеров, поэтому мне пришлось создать JsonArray, чтобы я мог выполнять итерацию по моим контейнерам и называть каждый пользовательский метод toJson().

Мораль истории: добавьте списки интерфейсов с помощью

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

трюк и итерация по списку контейнеров с использованием JsonArray (просто использование toJson() в списке не вызывает ваш специальный метод для детских контейнеров).

Определенно все еще ищет более естественное решение.

Счастливое кодирование

Ответ 2

Достаточно просто использовать пользовательский JsonSerializer, который явно получает класс объекта (предположительно Gson получает объявленный тип, по какой-то причине). Здесь общее решение для сериализации интерфейсов:

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

В вашем примере я могу сделать следующее:

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

Выводится:

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

Последний элемент является правильно сериализованной строкой.

Этого недостаточно для десериализации JSON, к сожалению, потому что в результате JSON не содержит информации о типе. Проверьте этот ответ на Как сериализовать класс с интерфейсом? для отслеживания типа объекта и, следовательно, сериализовать/десериализовать объект.

Ответ 3

Последняя версия Gson по-прежнему ведет себя так, как описано в исходном вопросе. (Я думаю, что я буду регистрировать запрос расширения, если он еще не существует.)

Для чего стоит Jackson десериализует как примерные структуры данных, так и ожидаемые. Для классов реализации Container и Animal просто нужны геттеры для второго примера.

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