Jackson 的@JsonSubTypes 是否仍然是多态反序列化所必需的?

Is Jackson's @JsonSubTypes still necessary for polymorphic deserialization?

我能够序列化和反序列化 class 层次结构,其中抽象基础 class 用

注释
@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS,
    include = JsonTypeInfo.As.PROPERTY,
    property = "@class")

但没有 @JsonSubTypes 列出 subclasses,并且 subclasses 本身相对没有注释,在构造函数上只有 @JsonCreator。 ObjectMapper 是普通的,我没有使用 mixin。

关于 PolymorphicDeserialization and "type ids" suggests (strongly) I need the @JsonSubTypes annotation on the abstract base class, or use it on a mixin, or that I need to register the subtypes with the ObjectMapper 的杰克逊文档。并且有很多 SO 问题 and/or 同意的博客文章。但它有效。 (这是 Jackson 2.6.0.)

所以...我是尚未记录的功能的受益者,还是我依赖​​于未记录的行为(可能会改变)或其他事情正在发生? (我问是因为我真的不希望它成为后两者中的任何一个。但是 I gots to know。)

编辑:添加代码 - 和一条评论。评论是:我应该提到我正在反序列化的所有 subclasses 都在与基本抽象 class.

相同的包和相同的 jar 中

抽象基础class:

package so;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS,
    include = JsonTypeInfo.As.PROPERTY,
    property = "@class")
public abstract class PolyBase
{
    public PolyBase() { }

    @Override
    public abstract boolean equals(Object obj);
}

它的子class:

package so;
import org.apache.commons.lang3.builder.EqualsBuilder;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public final class SubA extends PolyBase
{
    private final int a;

    @JsonCreator
    public SubA(@JsonProperty("a") int a) { this.a = a; }

    public int getA() { return a; }

    @Override
    public boolean equals(Object obj) {
        if (null == obj) return false;
        if (this == obj) return true;
        if (this.getClass() != obj.getClass()) return false;

        SubA rhs = (SubA) obj;
        return new EqualsBuilder().append(this.a, rhs.a).isEquals();
    }
}

Subclasses SubBSubC 除了字段 a 被声明为 String(不是 int)之外是相同的SubBboolean(不是 int)在 SubC 中(并且方法 getA 已相应修改)。

测试class:

package so;    
import java.io.IOException;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

public class TestPoly
{
    public static class TestClass
    {
        public PolyBase pb1, pb2, pb3;

        @JsonCreator
        public TestClass(@JsonProperty("pb1") PolyBase pb1,
                         @JsonProperty("pb2") PolyBase pb2,
                         @JsonProperty("pb3") PolyBase pb3)
        {
            this.pb1 = pb1;
            this.pb2 = pb2;
            this.pb3 = pb3;
        }

        @Override
        public boolean equals(Object obj) {
            if (null == obj) return false;
            if (this == obj) return true;
            if (this.getClass() != obj.getClass()) return false;

            TestClass rhs = (TestClass) obj;
            return new EqualsBuilder().append(pb1, rhs.pb1)
                                      .append(pb2, rhs.pb2)
                                      .append(pb3, rhs.pb3)
                                      .isEquals();
        }
    }

    @Test
    public void jackson_should_or_should_not_deserialize_without_JsonSubTypes() {

        // Arrange
        PolyBase pb1 = new SubA(5), pb2 = new SubB("foobar"), pb3 = new SubC(true);
        TestClass sut = new TestClass(pb1, pb2, pb3);

        ObjectMapper mapper = new ObjectMapper();

        // Act
        String actual1 = null;
        TestClass actual2 = null;

        try {
            actual1 = mapper.writeValueAsString(sut);
        } catch (IOException e) {
            fail("didn't serialize", e);
        }

        try {
            actual2 = mapper.readValue(actual1, TestClass.class);
        } catch (IOException e) {
            fail("didn't deserialize", e);
        }

        // Assert
        assertThat(actual2).isEqualTo(sut);
    }
}

此测试通过,如果您在第二行 try { 处中断,您可以检查 actual1 并查看:

{"pb1":{"@class":".SubA","a":5},
 "pb2":{"@class":".SubB","a":"foobar"},
 "pb3":{"@class":".SubC","a":true}}

所以三个子class被正确序列化(每个子class名称作为id)然后反序列化,结果比较相等(每个子class有一个"value type" equals()).

Jackson在序列化和反序列化中有两种实现多态的方法。它们在 第 1 节中定义。用法 在您发布的 link 中。

您的代码

@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS,
    include = JsonTypeInfo.As.PROPERTY,
    property = "@class")

是第二种方法的一个例子。首先要注意的是

All instances of annotated type and its subtypes use these settings (unless overridden by another annotation)

所以这个配置值传播到所有子类型。然后,我们需要一个类型标识符,它将 Java 类型映射到 JSON 字符串中的文本值,反之亦然。在您的示例中,这是由 JsonTypeInfo.Id#MINIMAL_CLASS

给出的

Means that Java class name with minimal path is used as the type identifier.

因此从目标实例生成一个最小的 class 名称,并在序列化时写入 JSON 内容。或者使用最小 class 名称来确定反序列化的目标类型。

您也可以使用 JsonTypeInfo.Id#NAME,其中

Means that logical type name is used as type information; name will then need to be separately resolved to actual concrete type (Class).

要提供这样的逻辑类型名称,您可以使用 @JsonSubTypes

Annotation used with JsonTypeInfo to indicate sub types of serializable polymorphic types, and to associate logical names used within JSON content (which is more portable than using physical Java class names).

这只是实现相同结果的另一种方法。您询问的有关状态的文档

Type ids that are based on Java class name are fairly straight-forward: it's just class name, possibly some simple prefix removal (for "minimal" variant). But type name is different: one has to have mapping between logical name and actual class.

因此,处理 class 名称的各种 JsonTypeInfo.Id 值非常简单,因为它们可以自动生成。但是,对于类型名称,您需要明确给出映射值。