java - 将多态对象列表反序列化为对象字段

标签 java json jackson polymorphic-deserialization

我的 REST 端点返回包含对象字段的响应对象。序列化一切都很好,但是当我开始为此 API 编写客户端时,我遇到了反序列化问题。我根据一些关于使用 Jackson 进行多态序列化的问题/文章制作了这个示例代码。它演示了这个问题。

@Data
abstract class Animal {

    String name;
}

@Data
class Dog extends Animal {

    boolean canBark;
}

@Data
class Cat extends Animal {

    boolean canMeow;
}

@Data
public class Zoo {

    private Object animals;
}


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "name", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(name = "dog", value = Dog.class),
        @JsonSubTypes.Type(name = "cat", value = Cat.class)
})
public class Mixin {

}

public class Main {

    private static final String JSON_STRING = "{\n"
            + "  \"animals\": [\n"
            + "    {\"name\": \"dog\"},\n"
            + "    {\"name\": \"cat\"}\n"
            + "  ]\n"
            + "}";

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = Jackson.newObjectMapper()
                .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
                .setDefaultPropertyInclusion(Include.NON_NULL);
        objectMapper.addMixIn(Animal.class, Mixin.class);
        Zoo zoo = objectMapper.readValue(JSON_STRING, Zoo.class);
        for (Object animal : (Collection) zoo.getAnimals()) {
            System.out.println(animal.getClass());
        }
    }
}

我对输出的期望(以及我对 List<Animals> 作为 Zoo#animals 类型的期望):

class jackson.poly.Dog
class jackson.poly.Cat

我现在拥有的对象:

class java.util.LinkedHashMap
class java.util.LinkedHashMap

但是除了动物列表之外,我还需要反序列化其他类型的对象。请帮忙!

最佳答案

不幸的是,反序列化器接受来自字段声明类型的类型。因此,如果字段是 Object,则它不会反序列化为任何内容,并将其保留为 Map。对此没有简单的解决方案,因为反序列化没有关于它应该是什么类型的信息。一种解决方案是自定义映射器,就像其中一个答案一样,或者您可以使用自定义 TaggedObject 类而不是我在类似用例中使用的 Object :

public class TaggedObject {

    @Expose
    private String type;
    @Expose
    private Object value;
    @Expose
    private Pair<TaggedObject, TaggedObject> pairValue;
    
    @SuppressWarnings("unchecked")
    public TaggedObject(Object v) {
        this.type = getTypeOf(v);
        if (v instanceof Pair) {
            this.pairValue = tagPair((Pair<Object, Object>) v);
            this.value = null;
        } else if ("long".equals(type)){
            this.value = "" + v;
            this.pairValue = null;
        } else {
            this.value = v;
            this.pairValue = null;
        }
    }

    private static Pair<TaggedObject, TaggedObject> tagPair(Pair<Object, Object> v) {
        return new Pair<TaggedObject, TaggedObject>(TaggedObject.tag(v.first), TaggedObject.tag(v.second));
    }

    private static String getTypeOf(Object v) {
        Class<?> cls = v.getClass();
        if (cls.equals(Double.class))
            return "double";
        if (cls.equals(Float.class))
            return "float";
        if (cls.equals(Integer.class))
            return "integer";
        if (cls.equals(Long.class))
            return "long";
        if (cls.equals(Byte.class))
            return "byte";
        if (cls.equals(Boolean.class))
            return "boolean";
        if (cls.equals(Short.class))
            return "short";
        if (cls.equals(String.class))
            return "string";
        if (cls.equals(Pair.class))
            return "pair";
        return "";
    }

    public static TaggedObject tag(Object v) {
        if (v == null)
            return null;
        return new TaggedObject(v);
    }

    public Object get() {
        if (type.equals("pair"))
            return new Pair<Object, Object>(
                    untag(pairValue.first),
                    untag(pairValue.second)
                    );
        return getAsType(value, type);
    }

    private static Object getAsType(Object value, String type) {
        switch (type) {
        case "string" : return value.toString();
        case "double" : return value;
        case "float"  : return ((Double)value).doubleValue();
        case "integer": return ((Double)value).intValue();
        case "long"   : {
            if (value instanceof Double)
                return ((Double)value).longValue();
            else
                return Long.parseLong((String) value);
        }
        case "byte"   : return ((Double)value).byteValue();
        case "short"  : return ((Double)value).shortValue();
        case "boolean": return value;
        }
        return null;
    }

    public static Object untag(TaggedObject object) {
        if (object != null)
            return object.get();
        return null;
    }
}

这是针对 google gson 的(这就是为什么有 @Expose 注释的原因)但应该可以与 jackson 一起正常工作。我没有包含 Pair 类,但您当然可以根据签名创建自己的类或省略它(我需要序列化对)。

关于java - 将多态对象列表反序列化为对象字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63339255/

相关文章:

java - 如何返回数组的副本?

Java 打印 : How do i go about it?

c# - 将 JSON.Net 用于 BSON 和 JSON 的最简单方法?

javascript - 在 Overwolf 中设置窗口位置

java - 在 Java 中是否可以一起返回枚举名称和值?

java - 无法处理 jackson 中复合键的托管/反向引用 'defaultreference'

java - 如何使用流比较列表的元素?

java - 使用 Jsch 检查 SFTP 权限

Python-向 json 文件添加元素

java - 如何查看 org.codehaus.jackson 日志消息 - 使用 logging.properties