ジャクソン - 双方向の関係

ジャクソン–双方向の関係

1. 概要

このチュートリアルでは、bidirectional relationships in Jacksonを処理するための最良の方法について説明します。

ジャクソンJSONの無限再帰の問題について説明し、次に–双方向の関係でエンティティをシリアル化する方法を確認し、最後に–それらを逆シリアル化します。

2. 無限再帰

まず、ジャクソンの無限再帰の問題を見てみましょう。 次の例では、「User」と「Item」の2つのエンティティとa simple one-to-many relationshipがあります。

User」エンティティ:

public class User {
    public int id;
    public String name;
    public List userItems;
}

Item」エンティティ:

public class Item {
    public int id;
    public String itemName;
    public User owner;
}

Item」のインスタンスをシリアル化しようとすると、JacksonはJsonMappingException例外をスローします。

@Test(expected = JsonMappingException.class)
public void givenBidirectionRelation_whenSerializing_thenException()
  throws JsonProcessingException {

    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    new ObjectMapper().writeValueAsString(item);
}

full exceptionは次のとおりです。

com.fasterxml.jackson.databind.JsonMappingException:
Infinite recursion (StackOverflowError)
(through reference chain:
org.example.jackson.bidirection.Item["owner"]
->org.example.jackson.bidirection.User["userItems"]
->java.util.ArrayList[0]
->org.example.jackson.bidirection.Item["owner"]
->…..

次のいくつかのセクションで、この問題を解決する方法を見てみましょう。

3. @JsonManagedReference@JsonBackReferenceを使用します

まず、関係に@JsonManagedReference@JsonBackReferenceの注釈を付けて、Jacksonが関係をより適切に処理できるようにします。

User」エンティティは次のとおりです。

public class User {
    public int id;
    public String name;

    @JsonBackReference
    public List userItems;
}

そして「Item」:

public class Item {
    public int id;
    public String itemName;

    @JsonManagedReference
    public User owner;
}

新しいエンティティをテストしてみましょう。

@Test
public void
  givenBidirectionRelation_whenUsingJacksonReferenceAnnotation_thenCorrect()
  throws JsonProcessingException {

    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, not(containsString("userItems")));
}

シリアル化の出力は次のとおりです。

{
 "id":2,
 "itemName":"book",
 "owner":
    {
        "id":1,
        "name":"John"
    }
}

ご了承ください:

  • @JsonManagedReferenceは、参照の前方部分であり、通常どおりシリアル化されます。

  • @JsonBackReferenceは参照の後ろの部分であり、シリアル化から省略されます。

4. @JsonIdentityInfoを使用する

では、@JsonIdentityInfoを使用して、双方向の関係を持つエンティティのシリアル化を支援する方法を見てみましょう。

User」エンティティにクラスレベルの注釈を追加します。

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class,
  property = "id")
public class User { ... }

そして、「Item」エンティティへ:

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class,
  property = "id")
public class Item { ... }

テストの時間:

@Test
public void givenBidirectionRelation_whenUsingJsonIdentityInfo_thenCorrect()
  throws JsonProcessingException {

    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, containsString("userItems"));
}

シリアル化の出力は次のとおりです。

{
 "id":2,
 "itemName":"book",
 "owner":
    {
        "id":1,
        "name":"John",
        "userItems":[2]
    }
}

5. @JsonIgnoreを使用する

または、@JsonIgnoreアノテーションを使用して単にignore one of the sides of the relationshipにすることもできます。これにより、チェーンが切断されます。

次の例では、「User」プロパティ「userItems」のシリアル化を無視することで、無限再帰を防ぎます。

User」エンティティは次のとおりです。

public class User {
    public int id;
    public String name;

    @JsonIgnore
    public List userItems;
}

そして、ここにテストがあります:

@Test
public void givenBidirectionRelation_whenUsingJsonIgnore_thenCorrect()
  throws JsonProcessingException {

    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, not(containsString("userItems")));
}

そして、シリアル化の出力は次のとおりです。

{
 "id":2,
 "itemName":"book",
 "owner":
    {
        "id":1,
        "name":"John"
    }
}

6. @JsonViewを使用する

新しい@JsonViewアノテーションを使用して、関係の片側を除外することもできます。

次の例では、InternalPublicを拡張するtwo JSON Views – Public and Internalを使用します。

public class Views {
    public static class Public {}

    public static class Internal extends Public {}
}

PublicビューにすべてのUserおよびItemフィールドを含めます–Internalビューに含まれるexcept the User field userItems

これが私たちのエンティティ「User」です:

public class User {
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String name;

    @JsonView(Views.Internal.class)
    public List userItems;
}

そして、これが私たちのエンティティ「Item」です。

public class Item {
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String itemName;

    @JsonView(Views.Public.class)
    public User owner;
}

Publicビューを使用してシリアル化すると、正しく機能します–because we excluded userItemsはシリアル化されません。

@Test
public void givenBidirectionRelation_whenUsingPublicJsonView_thenCorrect()
  throws JsonProcessingException {

    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writerWithView(Views.Public.class)
      .writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, not(containsString("userItems")));
}

ただし、Internalビューを使用してシリアル化すると、すべてのフィールドが含まれているため、JsonMappingExceptionがスローされます。

@Test(expected = JsonMappingException.class)
public void givenBidirectionRelation_whenUsingInternalJsonView_thenException()
  throws JsonProcessingException {

    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    new ObjectMapper()
      .writerWithView(Views.Internal.class)
      .writeValueAsString(item);
}

7. カスタムシリアライザーを使用する

次へ–カスタムシリアライザーを使用して双方向の関係でエンティティをシリアル化する方法を見てみましょう。

次の例では、カスタムシリアライザーを使用して「User」プロパティ「userItems」をシリアル化します。

User」エンティティは次のとおりです。

public class User {
    public int id;
    public String name;

    @JsonSerialize(using = CustomListSerializer.class)
    public List userItems;
}

そして、これが「CustomListSerializer」です。

public class CustomListSerializer extends StdSerializer>{

   public CustomListSerializer() {
        this(null);
    }

    public CustomListSerializer(Class t) {
        super(t);
    }

    @Override
    public void serialize(
      List items,
      JsonGenerator generator,
      SerializerProvider provider)
      throws IOException, JsonProcessingException {

        List ids = new ArrayList<>();
        for (Item item : items) {
            ids.add(item.id);
        }
        generator.writeObject(ids);
    }
}

次に、シリアライザーをテストして、適切な種類の出力が生成されることを確認しましょう。

@Test
public void givenBidirectionRelation_whenUsingCustomSerializer_thenCorrect()
  throws JsonProcessingException {
    User user = new User(1, "John");
    Item item = new Item(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, containsString("userItems"));
}

そして、カスタムシリアライザーを使用したシリアル化のthe final output

{
 "id":2,
 "itemName":"book",
 "owner":
    {
        "id":1,
        "name":"John",
        "userItems":[2]
    }
}

8. @JsonIdentityInfoで逆シリアル化

では、@JsonIdentityInfoを使用して双方向の関係を持つエンティティを逆シリアル化する方法を見てみましょう。

User」エンティティは次のとおりです。

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class,
  property = "id")
public class User { ... }

そして、「Item」エンティティ:

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class,
  property = "id")
public class Item { ... }

ここで、簡単なテストを作成しましょう。解析する手動のJSONデータから始めて、正しく構築されたエンティティで終了します。

@Test
public void givenBidirectionRelation_whenDeserializingWithIdentity_thenCorrect()
  throws JsonProcessingException, IOException {
    String json =
      "{\"id\":2,\"itemName\":\"book\",\"owner\":{\"id\":1,\"name\":\"John\",\"userItems\":[2]}}";

    ItemWithIdentity item
      = new ObjectMapper().readerFor(ItemWithIdentity.class).readValue(json);

    assertEquals(2, item.id);
    assertEquals("book", item.itemName);
    assertEquals("John", item.owner.name);
}

9. カスタムデシリアライザーを使用する

最後に、カスタムデシリアライザーを使用して、双方向の関係でエンティティをデシリアライズしましょう。

次の例では、カスタムデシリアライザーを使用して「User」プロパティ「userItems」を解析します。

User」エンティティは次のとおりです。

public class User {
    public int id;
    public String name;

    @JsonDeserialize(using = CustomListDeserializer.class)
    public List userItems;
}

そして、これが私たちの「CustomListDeserializer」です。

public class CustomListDeserializer extends StdDeserializer>{

    public CustomListDeserializer() {
        this(null);
    }

    public CustomListDeserializer(Class vc) {
        super(vc);
    }

    @Override
    public List deserialize(
      JsonParser jsonparser,
      DeserializationContext context)
      throws IOException, JsonProcessingException {

        return new ArrayList<>();
    }
}

そして簡単なテスト:

@Test
public void givenBidirectionRelation_whenUsingCustomDeserializer_thenCorrect()
  throws JsonProcessingException, IOException {
    String json =
      "{\"id\":2,\"itemName\":\"book\",\"owner\":{\"id\":1,\"name\":\"John\",\"userItems\":[2]}}";

    Item item = new ObjectMapper().readerFor(Item.class).readValue(json);

    assertEquals(2, item.id);
    assertEquals("book", item.itemName);
    assertEquals("John", item.owner.name);
}

10. 結論

このチュートリアルでは、Jacksonを使用して双方向の関係でエンティティをシリアル化/逆シリアル化する方法を説明しました。

これらすべての例とコードスニペットcan be found in our GitHub projectの実装–これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。