【问题标题】:How to configure Jackson to deserialize List containing named types annotated on Base Class with @JsonTypeInfo and JsonSubTypes?如何配置 Jackson 以反序列化包含使用 @JsonTypeInfo 和 JsonSubTypes 在基类上注释的命名类型的列表?
【发布时间】:2017-11-19 00:16:25
【问题描述】:

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

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

动物园

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. 我可以收集到的是反序列化不起作用,因为 ZooList&lt;Animal&gt; 的 getter 是基类类型,反序列化无法确定如何创建 DogElephant,并且基于在签名时它会生成Animal。但是我会认为通过放置JsonTypeNameJsonSubTypes 注释我已经标记了相关的子类。是这样吗?
  2. Animal 类是否必须定义为 abstract 才能正常工作?
  3. 让这个工作的唯一方法是实现类 Zoo 的自定义反序列化吗?这是一个很好的例子吗:https://www.sghill.net/how-do-i-write-a-jackson-json-serializer-deserializer.html

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

【问题讨论】:

  • 这个例子真的需要DefaultZoo吗(我们不知道它是什么)? Zoo 已经持有列表。
  • 这不是必须的,但这是目前的实现方式。我会更新并删除。

标签: java json jackson polymorphism deserialization


【解决方案1】:

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

您将Animal标记为

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

这告诉杰克逊 2 个矛盾的事情:

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

最简单的“修复”是改用JsonTypeInfo.Id.NAME,它会输出(我已经添加了Lombok的@ToStrings):

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

所以现在你有了正确的类型识别。请注意,在这种情况下,不需要子类上的 @JsonTypeName 注释,因为您已经指定了一种解析类型的方法:@JsonTypeInfo 告诉反序列化器查找 type 属性,@JsonSubTypes 告诉它映射它的值(name 属性)到它的类(value 属性)。您也不需要指定任何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 { ... }

很有用,例如,如果您无权访问超类。

另一种在没有更少注释的情况下完成所有这些的方法(同样,如果您无权访问类,这很有用)是配置映射器。您可以使用而不是 @JsonSubTypes

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

这里包括类型名称-类映射。

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

问题是我无法反序列化超类属性和子类属性。它是一个或另一个。也许自定义反序列化器确实可以解决这个问题。

【讨论】:

  • 谢谢@user1803551。缺少的扩展是一个错误,因为我正在将我的真实课程转换为其他课程。所以我更新了我的代码以包含它。我知道有不同的方法来处理多态性。我已经从子类中取出 JsonTypeName 并只留在基类上。但是我的问题仍然是能够反序列化所有字段的对象。我正在研究自定义反序列化器,如果它有效,我将发布我的答案。
【解决方案2】:

我按照@programerB 发布的stackoverflow 中的答案解决了这个问题(感谢Bruce): Dynamic polymorphic type handling with Jackson

我最终使用示例 6 来实现它。

  1. 我可以收集到的是反序列化不起作用,因为 Zoo for List 中的 getter 是基类类型,反序列化无法确定如何创建 Dog 或 Elephant,并根据签名生成 Animal。但是我会认为通过放置 JsonTypeName 和 JsonSubTypes 注释我已经标记了相关的子类。是这样吗?

Answer:我看到的问题是,当检索到 JSON Node 值时,json 中的类型将 " 作为字符串的一部分。我认为这就是为什么基于 JsonTypeName 的自动处理无法识别正确的子类的原因。

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

如果您在程序员布鲁斯解决方案中看到示例 6。为了识别正确的子类,使用以下代码块:

String name = element.getKey();
if (registry.containsKey(name))
{
  animalClass = registry.get(name);
  break;
}

这是我使用 element.getValue 来识别正确子类的地方。在比较值之前,我必须去掉引号(“)。另外,如果您使用的是 fastxml。您必须替换以下内容

return mapper.readValue(root, animalClass);

return mapper.readValue(root.toString(), animalClass);
  1. 是否必须将 Animal 类定义为抽象类才能正常工作?

Answer: Animal 类不需要是抽象的。非抽象类可以是基类。在我的例子中,我什至已经实例化了这个类。

  1. 让这个工作的唯一方法是实现类 Zoo 的自定义反序列化吗?这是一个很好的例子吗:https://www.sghill.net/how-do-i-write-a-jackson-json-serializer-deserializer.html

回答:根据我观察到的情况,自定义反序列化是处理此问题的最佳/简洁方式。 Animal 类而不是 Zoo 类需要自定义反序列化器。如上所述,要遵循的链接是:http://programmerbruce.blogspot.com.au/2011/05/deserialize-json-with-jackson-into.html

我可以将其标记为程序员布鲁斯回答的问题的重复,但我认为我解释问题并提供解决方案的方式可能会对其他人有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-25
    • 2016-03-09
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多