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

时间:2017-11-19 00:16:26

标签: java json jackson polymorphism deserialization

我研究了使用继承实现Objects类的最佳方法,并将其作为List包含在对象中。

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

动物园

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;
}

ModelHelper

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反序列化为Object:

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<Animal>的getter是基类类型,反序列化无法解决如何创建DogElephant,根据签名生成Animal。但是我想通过放JsonTypeNameJsonSubTypes注释我已经标记了相关的子类。是这样的吗?
  2. 是否必须将Animal类定义为abstract才能使其生效?
  3. 让这个工作的唯一方法是为类Zoo实现自定义反序列化吗?这是一个很好的例子:https://www.sghill.net/how-do-i-write-a-jackson-json-serializer-deserializer.html
  4. 如果代码不清楚,请告诉我,我会修复它。

2 个答案:

答案 0 :(得分:1)

我在@programerB(感谢Bruce)发布的stackoverflow回答中解决了问题: Dynamic polymorphic type handling with Jackson

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

  1. 我可以收集的是反序列化无效,因为Zoo for List中的getter是基类类型,反序列化无法解决如何创建Dog或Elephant,并基于签名生成Animal。但是我想通过放置JsonTypeName和JsonSubTypes注释我已经标记了相关的子类。是这样的吗?
  2. 答案:我看到的问题是进入json的类型有&#34;作为检索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来识别正确的子类的地方。在比较值之前,我不得不去除引号(&#34;)。此外,如果您使用的是fastxml。您必须替换以下

    return mapper.readValue(root, animalClass);
    

    return mapper.readValue(root.toString(), animalClass);
    
    1. 必须将Animal类定义为抽象才能使其正常工作吗?
    2. 答案:动物类不需要是抽象的。非抽象类可以是基类。在我的例子中,我甚至已经实例化了这个类。

      1. 让这个工作的唯一方法是为类Zoo实现自定义反序列化吗?这是一个很好的例子:https://www.sghill.net/how-do-i-write-a-jackson-json-serializer-deserializer.html
      2. 答案:基于我所观察到的,自定义反序列化是处理此问题的最佳/最佳方式。自定义反序列化器是Animal类所必需的,而不是Zoo类。如上所述,要遵循的链接是:http://programmerbruce.blogspot.com.au/2011/05/deserialize-json-with-jackson-into.html

        我可以将此标记为programmerbruce所回答的问题的副本,但我认为我解释问题并提供解决方案的方式可能会帮助其他人。

答案 1 :(得分:0)

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

您使用

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

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

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

最简单的&#34;修复&#34;是使用JsonTypeInfo.Id.NAME代替,它将输出(我已经添加了Lombok&#39; s @ToString):

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

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

当时使用@JsonTypeName是什么?它取代了name中的@JsonSubTypes

@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"));

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

最有可能采用更多方法来混合配置,但这足以证明这一点并回答您的问题。

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