一尘不染

对于多态反序列化,Jackson的@JsonSubTypes是否仍然必要?

json

我能够序列化和反序列化一个类层次结构,其中抽象基类用

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

但是没有@JsonSubTypes列出子类,并且子类本身是相对未注释的,仅@JsonCreator在构造函数上具有a
。ObjectMapper是香草的,我没有使用mixin。

Jackson关于PolymorphicDeserialization和“ type
id”的
文档建议(强烈)我需要在@JsonSubTypes抽象基类上使用批注,或者在mixin上使用它,或者需要在ObjectMapper中注册子类型。并且有很多这样的问题和/或博客帖子都可以达成共识。但这有效。(
这是杰克逊2.6.0。

那么…我是尚未记录的功能的受益者,还是我依靠未记录的行为(可能会更改)还是发生了其他事情?(我问,是因为我真的不希望它成为后两者之一。但是我必须知道。)

编辑:添加代码-和一个注释。评论是:我应该提到,我要反序列化的所有子类都与基本抽象类位于同一个程序包和相同的jar中。

抽象基类:

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

它的一个子类:

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

子类SubBSubC相同,除了在(中)a声明了String(不是intSubB和在中boolean(未声明了intSubC(并且相应地getA修改了方法)。

测试类别:

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

因此,对三个子类进行了正确的序列化(每个子类以其类名作为id),然后进行反序列化,然后将结果进行比较以得出相等的结果(每个子类具有“值类型”
equals())。


阅读 585

收藏
2020-07-27

共1个答案

一尘不染

使用Jackson可以通过两种方法在序列化和反序列化中实现多态。它们在发布的链接的 第1部分
中定义。

您的密码

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

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

带注释的类型及其子类型的所有实例都使用这些设置(除非被另一个注释覆盖)

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

意味着将具有最小路径的Java类名称用作类型标识符。

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

您可能还使用JsonTypeInfo.Id#NAME

表示将 逻辑类型名称 用作类型信息;然后,需要将名称单独解析为实际的具体类型(Class)。

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

JsonTypeInfo一起使用的注释指示可序列化多态类型的子类型,并 关联 JSON内容中使用的 逻辑名
(比使用物理Java类名更易于移植)。

这只是获得相同结果的另一种方法。您正在询问状态的文档

基于Java类名的类型ID相当简单:它只是类名,可能是一些简单的前缀删除(“最小”变体)。但是类型名称是不同的:必须在逻辑名称和实际类之间进行映射。

因此JsonTypeInfo.Id,处理类名称的各种值很简单,因为它们可以自动生成。但是,对于类型名称,您需要显式提供映射值。

2020-07-27