【问题标题】:Is Jackson's @JsonSubTypes still necessary for polymorphic deserialization?杰克逊的@JsonSubTypes 是否仍然需要多态反序列化?
【发布时间】:2015-10-18 09:04:30
【问题描述】:

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

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

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

PolymorphicDeserialization and "type ids" 上的杰克逊文档(强烈)建议我需要抽象基类上的 @JsonSubTypes 注释,或者在 mixin 上使用它,或者我需要 register the subtypes with the ObjectMapper。并且有很多同意的问题和/或博客文章。然而它确实有效。 (这是 Jackson 2.6.0。

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

编辑:添加代码 - 和一条评论。评论是:我应该提到我要反序列化的所有子类都与基本抽象类在同一个包和同一个 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 是相同的,只是字段aSubBSubB 中声明为String(不是int)和boolean(不是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);
    }
}

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

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

所以三个子类被正确序列化(每个子类都以它们的类名作为id)然后反序列化,结果比较相等(每个子类都有一个“值类型”equals())。

【问题讨论】:

    标签: java json jackson json-deserialization


    【解决方案1】:

    Jackson 有两种方法可以在序列化和反序列化中实现多态性。它们在您发布的link 中的第 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 值是直截了当的,因为它们可以自动生成。但是,对于类型名称,您需要明确给出映射值。

    【讨论】:

    • 感谢您解释仅当您使用 NAME 表单时才需要 @JsonSubTypes。我阅读的所有文档,但我没有弄清楚...
    • 这里的遗憾是,如果您使用名称形式(这是非常可取的),您不能动态添加类或稍后添加类。
    • 在各个具体类上使用 JsonTypeInfo 不是更有意义吗?在接口上使用它似乎违反了我认为的开闭原则(因为我们需要在每次添加具体实现时修改我们的接口)。如果这里遗漏了什么,请纠正我
    • @HDave:您可以使用ObjectMapper#registerSubtypes,可能与反射类似。
    • 如c-c所说,我们成功使用@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, property="message_type"),然后用新创建的映射器注册每个类:mapper.registerSubtypes(new NamedType(SomeClass.class, "some_class"));。当您在运行时知道所有类但无法更改基类时,此方法有效,因此您无需在 JsonSubTypes 中列出所有内容。
    猜你喜欢
    • 1970-01-01
    • 2015-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-13
    • 2014-08-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多