为什么jackson多态序列化在列表中不起作用?

jackson正在做一些真正奇怪的事情,我找不到任何解释。 我正在进行多态序列化,当一个对象独立时它可以很好地工作。 但是,如果将相同的对象放入列表并对列表进行序列化,则会删除类型信息。

它丢失类型信息的事实将导致人们怀疑类型擦除。 但这是在列表内容的序列化过程中发生的; jackson所要做的就是检查它正在序列化的当前对象以确定其类型。

我用Jackson 2.5.1创建了一个例子:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.ArrayList; import java.util.List; public class Test { @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY) @JsonSubTypes({ @Type(value = Dog.class, name = "dog"), @Type(value = Cat.class, name = "cat")}) public interface Animal {} @JsonTypeName("dog") public static class Dog implements Animal { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } @JsonTypeName("cat") public static class Cat implements Animal { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } public static void main(String[] args) throws JsonProcessingException { List list = new ArrayList(); list.add(new Cat()); System.out.println(new ObjectMapper().writeValueAsString(list)); System.out.println(new ObjectMapper().writeValueAsString(list.get(0))); } } 

这是输出:

 [{"name":null}] {"@type":"cat","name":null} 

如您所见,当对象在列表中时,Jackson不会添加类型信息。 有谁知道为什么会这样?

这里和这里讨论了发生这种情况的各种原因。 我不一定同意这些原因,但jackson因为类型擦除而不知道List (或CollectionMap )包含的元素类型。 它选择使用不解释注释的简单序列化程序。

您可以在这些链接中建议两个选项:

首先,您可以创建一个实现List的类,适当地实例化它并序列化实例。

 class CatList implements List {...} 

generics类型参数Cat不会丢失。 jackson可以使用它并使用它。

其次,您可以为List类型实例化和使用ObjectWriter 。 例如

 System.out.println(new ObjectMapper().writerFor(new TypeReference>() {}).writeValueAsString(list)); 

将打印

 [{"@type":"cat","name":"heyo"}] 

Sotirios Delimanolis给出的答案是正确的。 但是,我认为将此解决方法作为单独的答案发布是很好的。 如果您所处的环境中无法为需要返回的每种类型的东西更改ObjectMapper(如Jersey / SpringMVC webapp),则可以选择其他方式。

您可以在包含该类型的类中包含一个私有final字段。 该字段对于类外的任何内容都不可见,但如果使用@JsonProperty("@type") (或“@class”或任何类型字段命名)注释它,Jackson将序列化它,无论对象在何处位于。

 @JsonTypeName("dog") public static class Dog implements Animal { @JsonProperty("@type") private final String type = "dog"; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } 

由于数组不使用类型擦除,您可以解决它覆盖ObjectMapper.writeValueAsString以将Collection转换为Array。

 public class CustomObjectMapper extends ObjectMapper { @Override public String writeValueAsString(Object value) throws JsonProcessingException { //Transform Collection to Array to include type info if(value instanceof Collection){ return super.writeValueAsString(((Collection)value).toArray()); } else return super.writeValueAsString(value); } } 

将它用于Test.main:

 System.out.println(new CustomObjectMapper().writeValueAsString(list)); 

输出(猫狗名单):

 [{"@type":"cat","name":"Gardfield"},{"@type":"dog","name":"Snoopy"}]