Сериализованный список интерфейсов 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 в правильный тип, используя собственный десериализатор. Это сериализация, которая дает мне проблемы.
Спасибо
Ответы
Ответ 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;
}
}