1. 概要

このチュートリアルでは、Jacksonライブラリの演繹ベースのポリモーフィズム機能を使用する方法を探ります。

2. 名前ベースのポリモーフィズム

次の画像で説明されているようなクラスの構造があると想像してみてください。

まず、NamedCharacterImperialSpyクラスCharacterインターフェイスを実装します。次に、KingクラスとKnightクラスNamedCharacterクラスを実装します。 最後に、 ControlledCharacterクラスがあります。これには、プレーヤーが制御するキャラクターへの参照が含まれています。

受信したJSONの構造を変更せずに、JSONオブジェクトをJavaオブジェクトに解析したいと思います。

それでは、クラスの定義を見てみましょう。 ベースインターフェイスの場合、 使用する控除を宣言するには、Jacksonアノテーションを使用する必要があります。 また、@ JsonSubTypesアノテーションを追加して、差し引くクラスを宣言する必要があります。

@JsonTypeInfo(use = Id.NAME)
@JsonSubTypes({ @Type(ImperialSpy.class), @Type(King.class), @Type(Knight.class) })
public interface Character {
}

さらに、インターフェイスCharacterKingおよびKnightクラスの間に中間クラスを設定することもできます。 したがって、ジャクソン、この場合もポリモーフィズムを差し引く方法もわかります。

public class NamedCharacter implements Character {
    private String name;

    // standard setters and getters
}

続いて、Character interfaceのサブクラスを実装します。 前のコード例では、これらのサブクラスをサブタイプとしてすでに宣言しています。 したがって、実装にはJacksonライブラリへの依存関係はありません。

public class ImperialSpy implements Character {
}
public class King extends NamedCharacter {
    private String land;

    // standard setters and getters
}
public class Knight extends NamedCharacter {
    private String weapon;

    // standard setters and getters
}

マップしたいJSONの例は次のとおりです。

{
    "name": "Old King Allant",
    "land": "Boletaria",
}

まず、上記のJSON構造を読み取ろうとすると、Jacksonは [simple type、class com.baeldung.jackson.deductionbasedpolymorphism.Character]:missing typeidpropertyのサブタイプを解決できませんでしたというメッセージとともにランタイム例外をスローします。 @type’:

@Test
void givenAKingWithoutType_whenMapping_thenExpectAnError() {
    String kingJson = formatJson("{'name': 'Old King Allant', 'land':'Boletaria'}");
    assertThrows(InvalidTypeIdException.class, () -> objectMapper.readValue(kingJson, Character.class));
}

さらに、 formatJson ユーティリティメソッドは、 JSON が必要とするように、引用符文字を二重引用符に変換することにより、テストのコードを単純に保つのに役立ちます。

public static String formatJson(String input) {
    return input.replaceAll("'", "\"");
}

したがって、文字のタイプを多態的に差し引くことができるようにするには、JSON構造を変更し、オブジェクトのタイプを明示的に追加する必要があります。 したがって、多態的な動作をJSON構造と組み合わせる必要があります。

{
    "@type": "King"
    "name": "Old King Allant",
    "land": "Boletaria",
}
@Test
void givenAKing_whenMapping_thenExpectAKingType() throws Exception {
    String kingJson = formatJson("{'name': 'Old King Allant', 'land':'Boletaria', '@type':'King'}");

    Character character = objectMapper.readValue(kingJson, Character.class);
    assertTrue(character instanceof King);
    assertSame(character.getClass(), King.class);
    King king = (King) character;
    assertEquals("Boletaria", king.getLand());
}

3. 推論ベースのポリモーフィズム

控除ベースのポリモーフィズムをアクティブ化するには、 @JsonTypeInfo(use = Id.DEDUCTION):を使用するだけです。

@JsonTypeInfo(use = Id.DEDUCTION)
@JsonSubTypes({ @Type(ImperialSpy.class), @Type(King.class), @Type(Knight.class) })
public interface Character {
}

4. 単純な推論

単純な推論を使用して、JSONを多態的に読み取る方法を調べてみましょう。 読みたいオブジェクトは次のとおりです。

{
    "name": "Ostrava, of Boletaria",
    "weapon": "Rune Sword",
}

まず、Characterオブジェクトの値を読み取ります。 次に、JacksonがJSONのタイプを正しく差し引いたことをテストします。

@Test
void givenAKnight_whenMapping_thenExpectAKnightType() throws Exception {
    String knightJson = formatJson("{'name':'Ostrava, of Boletaria', 'weapon':'Rune Sword'}");

    Character character = objectMapper.readValue(knightJson, Character.class);

    assertTrue(character instanceof Knight);
    assertSame(character.getClass(), Knight.class);
    Knight king = (Knight) character;
    assertEquals("Ostrava, of Boletaria", king.getName());
    assertEquals("Rune Sword", king.getWeapon());
}

さらに、JSONが空のオブジェクトである場合、Jacksonはそれを ImperialSpy として解釈します。これは、属性のないクラスです。

@Test
void givenAnEmptyObject_whenMapping_thenExpectAnImperialSpy() throws Exception {
    String imperialSpyJson = "{}";

    Character character = objectMapper.readValue(imperialSpyJson, Character.class);

    assertTrue(character instanceof ImperialSpy);
}

また、 null JSONオブジェクトは、Jacksonによってnullオブジェクトとして差し引かれます。

@Test
void givenANullObject_whenMapping_thenExpectANullObject() throws Exception {
    Character character = objectMapper.readValue("null", Character.class);

    assertNull(character);
}

5. 大文字と小文字を区別しない推論

属性の場合がと一致しない場合でも、ジャクソンはポリモーフィズムを差し引くこともできます。 まず、ACCEPT_CASE_INSENSITIVE_PROPERTIESを有効にしてオブジェクトマッパーをインスタンス化します。

ObjectMapper objectMapper = JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build();

次に、インスタンス化された objectMapper、を使用して、ポリモーフィズムが正しく差し引かれていることをテストできます。

{
    "NaMe": "Ostrava, of Boletaria",
    "WeaPON": "Rune Sword",
}
@Test
void givenACaseInsensitiveKnight_whenMapping_thenExpectKnight() throws Exception {
    String knightJson = formatJson("{'NaMe':'Ostrava, of Boletaria', 'WeaPON':'Rune Sword'}");

    Character character = objectMapper.readValue(knightJson, Character.class);

    assertTrue(character instanceof Knight);
    assertSame(character.getClass(), Knight.class);
    Knight knight = (Knight) character;
    assertEquals("Ostrava, of Boletaria", knight.getName());
    assertEquals("Rune Sword", knight.getWeapon());
}

6. 含まれる推論

他のオブジェクトに含まれているオブジェクトのポリモーフィズムを差し引くこともできます ControlledCharacter クラス定義を使用して、次のJSONのマッピングを示します。

{
    "character": {
        "name": "Ostrava, of Boletaria",
        "weapon": "Rune Sword"
    }
}
@Test
void givenAKnightControlledCharacter_whenMapping_thenExpectAControlledCharacterWithKnight() throws Exception {
    String controlledCharacterJson = formatJson("{'character': {'name': 'Ostrava, of Boletaria', 'weapon': 'Rune Sword'}}");

    ControlledCharacter controlledCharacter = objectMapper.readValue(controlledCharacterJson, ControlledCharacter.class);
    Character character = controlledCharacter.getCharacter();

    assertTrue(character instanceof Knight);
    assertSame(character.getClass(), Knight.class);
    Knight knight = (Knight) character;
    assertEquals("Ostrava, of Boletaria", knight.getName());
    assertEquals("Rune Sword", knight.getWeapon());
}

7. 結論

このチュートリアルでは、ジャクソンライブラリを使用して演繹ベースのポリモーフィズムを使用する方法を探りました。

記事に付属するソースコードは、GitHubのにあります。