序列化接口列表 GSON

2022-09-02 22:21:37

我在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可以序列化接口的列表,当该列表本身,但是当该列表包含在另一个类中时,GSON就会出现问题。List<Interface>

知道我做错了什么吗?

作为旁注,我可以使用自定义反序列化程序将json字符串正确地反序列化为正确的类型。正是序列化给我带来了问题。

谢谢


答案 1

只需使用显式获取对象类的自定义 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 不包含任何类型信息。请查看如何使用接口序列化类的答案?,了解跟踪对象类型并因此序列化/反序列化对象的方法。


答案 2

它远非漂亮,但我现在使用的解决方案是使用

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()不会在子容器上调用您的特殊方法)。

肯定仍然在寻找更自然的解决方案。

快乐编码