web-dev-qa-db-ja.com

ジャクソンの@JsonSubTypesは多相デシリアライゼーションにまだ必要ですか?

抽象基本クラスに注釈が付けられているクラス階層をシリアライズおよびデシリアライズできます

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

ただし、サブクラスをリストする_@JsonSubTypes_はありません。サブクラス自体には比較的注釈が付けられておらず、コンストラクターには_@JsonCreator_しかありません。 ObjectMapperはVanillaであり、私はmixinを使用していません。

PolymorphicDeserialization and "type ids" に関するJacksonのドキュメントは、抽象ベースクラスで_@JsonSubTypes_アノテーションが必要か、mixinでそれを使用するか、または必要なことを(強く)示唆しています サブタイプをObjectMapperに登録します 。そして、多くのSOの質問やブログ投稿に同意します。それでも動作します。(これはジャクソン2.6.0です。

だから...私はまだ文書化されていない機能の受益者ですか、それとも文書化されていない動作(変更される可能性があります)に依存していますか? (私は本当に後者の2つになりたくないので尋ねています。しかし、 私は知るようになりました 。)

編集:コードの追加-1つのコメント。コメントは次のとおりです。デシリアライズするすべてのサブクラスは、基本抽象クラスと同じパッケージおよび同じ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は、フィールドaStringintSubBではなく)宣言されていることを除いて同じです。 booleanintSubCではありません)(およびメソッド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);
    }
}
_

このテストに合格し、2番目の_try {_行でブレークすると、_actual1_を検査して次を確認できます。

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

そのため、3つのサブクラスは適切にシリアル化され(それぞれクラス名がidである)、逆シリアル化され、結果は等しく比較されました(各サブクラスには「値型」equals()があります)。

43
davidbak

Jacksonでのシリアル化と非シリアル化でポリモーフィズムを実現するには、2つの方法があります。これらはセクション1.使用法link で定義されています。

あなたのコード

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

2番目のアプローチの例です。最初に注意することは

注釈付きタイプとそのサブタイプのすべてのインスタンスは、これらの設定を使用します(別の注釈でオーバーライドされない限り)

したがって、この構成値はすべてのサブタイプに伝搬します。次に、Java型をJSON文字列のテキスト値に、またはその逆にマップする型識別子が必要です。あなたの例では、これは JsonTypeInfo.Id#MINIMAL_CLASS で与えられます

最小パスのJavaクラス名がタイプ識別子として使用されることを意味します。

そのため、ターゲットインスタンスから最小クラス名が生成され、シリアル化時にJSONコンテンツに書き込まれます。または、最小限のクラス名を使用して、逆シリアル化のターゲットタイプを決定します。

JsonTypeInfo.Id#NAME whichも使用できます

論理型名が型情報として使用されることを意味します。その場合、nameは実際の具象型(Class)に個別に解決する必要があります。

このような論理型名を指定するには、 @JsonSubTypes を使用します

JsonTypeInfoで使用されるアノテーションは、シリアル化可能な多相型のサブタイプを示し、JSONコンテンツ内で使用される論理名の関連付けに使用します(物理Javaクラス名を使用するよりも移植性が高い) 。

これは、同じ結果を達成するための別の方法です。状態について尋ねているドキュメント

Javaクラス名に基づくタイプIDはかなり単純です。クラス名にすぎず、単純なプレフィックスの削除(「最小」バリアントの場合)である可能性があります。ただし、型名は異なります。論理名と実際のクラスをマッピングする必要があります。

したがって、クラス名を処理するさまざまなJsonTypeInfo.Id値は、自動生成できるため簡単です。ただし、型名については、マッピング値を明示的に指定する必要があります。

47