杰克逊的@JsonSubTypes仍然是多态反序列化所必需的吗?

dav*_*bak 43 java json jackson json-deserialization

我能够序列化和反序列化抽象基类注释的类层次结构

@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS,
    include = JsonTypeInfo.As.PROPERTY,
    property = "@class")
Run Code Online (Sandbox Code Playgroud)

但没有@JsonSubTypes列出子类,子类本身相对未注释,只有一个@JsonCreator在构造函数上.ObjectMapper是vanilla,我没有使用mixin.

关于PolymorphicDeserialization和"type id"的 Jackson文档建议(强烈地)我需要在@JsonSubTypes抽象基类上使用注释,或者在mixin上使用它,或者我需要使用ObjectMapper注册子类型.并且有很多SO问题和/或博客帖子都同意.但它确实有效.(这是Jackson 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);
}
Run Code Online (Sandbox Code Playgroud)

它的一个子类:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

子类SubBSubC是不同的场相同的a声明String(未int)中SubBboolean(未int)在SubC(并且该方法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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

{"pb1":{"@class":".SubA","a":5},
 "pb2":{"@class":".SubB","a":"foobar"},
 "pb3":{"@class":".SubC","a":true}}
Run Code Online (Sandbox Code Playgroud)

因此,三个子类被正确序列化(每个子类的类名称为id),然后反序列化,结果比较相等(每个子类都有一个"值类型" equals()).

Sot*_*lis 49

有两种方法可以实现与Jackson的序列化和反序列化的多态性.它们在第1节中定义.在您发布的链接中使用.

你的代码

@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS,
    include = JsonTypeInfo.As.PROPERTY,
    property = "@class")
Run Code Online (Sandbox Code Playgroud)

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

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

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

表示具有最小路径的Java类名称用作类型标识符.

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

你也可以使用JsonTypeInfo.Id#NAME哪个

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

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

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

这只是实现相同结果的另一种方式.您询问有关州的文件

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

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

  • 这里的令人失望的是,如果你使用的名称形式(这是远远优于),你不能动态地添加或者更新的添加类. (8认同)
  • 不在个别具体类上使用JsonTypeInfo更有意义吗?在界面上使用它似乎违反了我认为的开放关闭原则(因为我们需要在每次添加具体实现时修改我们的接口).如果我在这里遗漏了什么,请纠正我 (4认同)
  • 感谢您解释,仅当您使用 NAME 表单时才需要 @JsonSubTypes。我读过所有这些文档,但我没有弄清楚...... (2认同)
  • 正如 cc 提到的,我们成功使用了 `@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, property="message_type")`,然后使用新创建的映射器注册每个类: `mapper.registerSubtypes(new NamedType(SomeClass.class) ,“some_class”));`。当您在运行时知道所有类但无法更改基类时,此方法有效,因此您无需在 JsonSubTypes 中列出所有内容。 (2认同)
  • 这里显示了前面两条评论所讨论内容的示例:/sf/answers/3921445251/。 (2认同)