java - 如何配置 Jackson 以反序列化包含在基类上使用 @JsonTypeInfo 和 JsonSubTypes 注释的命名类型的列表?

标签 java json jackson polymorphism deserialization

我已经研究了实现对象类的最佳方式,继承并包含在一个对象中作为一个列表。

如果您知道处理该场景并提供正确答案的任何链接,我将很乐意将此视为重复并关闭此项目。 我有以下类(class):

动物园

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.List;
import lombok.*;
@Getter
@Setter
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
    public class Zoo {
        private String zooName;
        private String zooLocation;
        @JsonInclude(JsonInclude.Include.NON_EMPTY)
        private @Singular List<Animal> animals;
}

动物

@Getter
@Setter
@Builder(builderMethodName = "parentBuilder")
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(
    use = JsonTypeInfo.Id.NONE,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type",
    defaultImpl = Animal.class
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "dog"),
    @JsonSubTypes.Type(value = Elephant.class, name = "elephant")
})
public class Animal{
    private String type;
    private String nameTag;
}

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({ "type", "nameTag"})
@JsonTypeName("dog")
public class Dog extends Animal{
    private String barkingPower;
}

大象

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({ "type", "nameTag"})
public class Elephant extends Animal{
    private String weight;
}

模型助手

public final class ModelHelper {
    private static final ObjectMapper MAPPER = new ObjectMapper();        
        .configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false)        
        .setSerializationInclusion(JsonInclude.Include.NON_NULL)    
        .enableDefaultTypingAsProperty(
            ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT,    "type");
    public static <T> T fromJson(@NotNull final String json, final Class<T> objectClass) {
          try {
            return MAPPER.readValue(json, objectClass);
        } catch (Exception e) {
            final String message = String.format("Error de-serialising JSON '%s' to object of %s",
                    json, objectClass.getName());
                log.warn(message, e);
            return null;
        }
    }
}

我正在使用 ModelHelper 将 JSON 反序列化为一个对象:

String json = "{\"zooName\":\"Sydney Zoo\",\"zooLocation\":\"Sydney\",\"animals\":[{\"type\":\"dog\",\"nameTag\":\"Dog1\",\"barkingPower\":\"loud\"},{\"type\":\"elephant\",\"nameTag\":\"Elephant1\",\"weight\":\"heavy\"}]}";
mapper.readValue(json, Zoo.class);

当前反序列化Zoo只返回 Animal属性而不是 DogElephant属性。

我的问题是:

  1. 我可以收集到的是反序列化不起作用,因为 Zoo 中的 getter对于 List<Animal>是基类类型,反序列化无法计算出如何创建 DogElephant ,并根据签名生成 Animal .但我会认为通过输入 JsonTypeNameJsonSubTypes注释我已经标记了相关的子类。是这样吗?
  2. 是否Animal类必须定义为 abstract这行得通吗?
  3. 使它起作用的唯一方法是为类 Zoo 实现自定义反序列化吗?这是一个很好的例子吗:https://www.sghill.net/how-do-i-write-a-jackson-json-serializer-deserializer.html

如果代码不清楚,请告诉我,我会修复它。

最佳答案

您混合了大约 3 种方法来反序列化多态类型,考虑到 Jackson 经历了多少次迭代,这并不奇怪。

您将 Animal 标记为

@JsonTypeInfo(use = JsonTypeInfo.Id.NONE, include = JsonTypeInfo.As.PROPERTY, property = "type")

这告诉 Jackson 2 自相矛盾的事情:

  • includeproperty 字段声明您要使用名为 type 的属性
  • use 字段声明不应使用任何 ID 机制,从而有效地忽略您的类型信息。

最简单的“修复”是改用 JsonTypeInfo.Id.NAME,这将输出(我添加了 Lombok 的 @ToString):

Zoo(zooName=Sydney Zoo, zooLocation=Sydney, animals=[Dog(barkingPower=loud), Elephant(weight=heavy)])

现在你有了正确的类型识别。请注意,在这种情况下,不需要子类上的 @JsonTypeName 注释,因为您已经指定了一种解析类型的方法:@JsonTypeInfo 告诉反序列化器寻找一个type 属性和 @JsonSubTypes 告诉它将它的值(name 属性)映射到它的类(value属性(property))。您也不需要指定任何 ObjectMapper 配置。

@JsonTypeName 是干什么用的?它替换了 @JsonSubTypes 中的 name:

@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "dog"),
    @JsonSubTypes.Type(value = Elephant.class) // no name
})
public class Animal { ... }

public class Dog extends Animal { ... }

@JsonTypeName("elephant") // name is here
public class Elephant extends Animal { ... }

这很有用,例如,如果您无权访问父类(super class)。

另一种无需注释即可完成所有这些操作的方法(同样,如果您无权访问类,这很有用)是配置映射器。您可以使用 @JsonSubTypes 而不是

MAPPER.registerSubtypes(new NamedType(Dog.class, "dog"), new NamedType(Elephant.class, "elephant"));

此处包括类型名称-类映射。

很可能有更多混合配置的方法,但这充分说明了这一点并回答了您的问题。

问题是我无法将父类(super class)属性与子类属性一起反序列化。它是一个或另一个。也许自定义反序列化器确实可以解决这个问题。

关于java - 如何配置 Jackson 以反序列化包含在基类上使用 @JsonTypeInfo 和 JsonSubTypes 注释的命名类型的列表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47372486/

相关文章:

java - 对象映射器 : Do not escape

java - 同时运行 2 个增强的 for 循环

java - Java Swing 中冲突的助记符

java - 生成具有六个前导零的 MD5 哈希

javascript - 无法从 jquery 中的 url 获取 JSON 数据作为 phonegap 应用程序的文本?

Java - POJO 序列化 Jackson 2.0

java - JsonNode 将 JSON 转换为字符串列表

javascript - 使用 Qlik Engine JSON API 导出可视化数据

android - 如何从 Retrofit2 调用 OnResponse 返回 Java 对象

java - 更换 Xstream 设施