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

2022-09-01 15:29:41

傑克遜正在做一些真正奇怪的事情,我找不到任何解釋。我正在进行多态序列化,当对象独立时,它可以完美地工作。但是,如果将同一对象放入列表中并改为序列化该列表,则会擦除类型信息。

它丢失类型信息的事实会导致人们怀疑类型擦除。但是,这是在序列化列表内容期间发生的;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<Cat> 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 不会添加类型信息。有谁知道为什么会发生这种情况?


答案 1

这里这里讨论了发生这种情况的各种原因。我不一定同意这些原因,但是杰克逊,由于类型擦除,他并不了解(或或)包含的元素类型。它选择使用不解释批注的简单序列化程序。ListCollectionMap

这些链接中建议了两个选项:

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

class CatList implements List<Cat> {...}

泛型类型参数不会丢失。杰克逊可以访问它并使用它。Cat

其次,您可以实例化并使用 类型 。例如ObjectWriterList<Cat>

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

将打印

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

答案 2

Sotirios Delimanolis给出的答案是正确的。但是,我认为将此解决方法作为单独的答案发布会很好。如果您所处的环境无法为需要返回的每种类型的内容更改对象映射器(例如泽西岛/ SpringMVC webapp),则有另一种选择。

您只需在包含该类型的类上包含私有 final 字段即可。该字段对类外的任何内容都不可见,但是如果您使用(或“@class”或您的类型字段命名的任何内容)对其进行注释,则无论对象位于何处,Jackson 都将对其进行序列化。@JsonProperty("@type")

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