1概要

このチュートリアルでは、Jacksonでの双方向の関係を扱うための最善の方法について説明します。

Jackson JSONの無限再帰問題について説明します。次に、双方向の関係を持つエンティティをシリアル化する方法について説明し、最後にそれらのシリアル化を解除します。


2無限再帰

まず、Jacksonの無限再帰問題を見てみましょう。次の例では、2つのエンティティ – ”

User

“と ”

Item

“があり、

単純な一対多の関係

があります。



User

”エンティティ:

public class User {
    public int id;
    public String name;
    public List<Item> 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);
}

  • 完全な例外** は次のとおりです。

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

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


3

@ JsonManagedReference



@ JsonBackReference


を使用してください.

最初に、Jacksonが関係をより適切に処理できるように、関係に

@ JsonManagedReference



@ JsonBackReference

の注釈を付けます。

これが「

User

」エンティティです。

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

    @JsonBackReference
    public List<Item> userItems;
}

そして「

項目

」:

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

アノテーションを使用して、関係の側面の1つを単純に無視し、連鎖を壊すこともできます。

次の例では、シリアル化から“

User

”プロパティ“

userItems

”を無視して、無限再帰を防ぎます。

これが「

User

」エンティティです。

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

    @JsonIgnore
    public List<Item> 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

アノテーションを使用して、関係の一方を除外することもできます。

次の例では、

Public



Internal

** の2つのJSONビューを使用します。ここで、

Internal



Public

を拡張します。

public class Views {
    public static class Public {}

    public static class Internal extends Public {}
}


パブリックビューに含まれる予定の

User

フィールド

userItems

** を除く、すべての

User

フィールドおよび

Item

フィールドを

Public__ビューに含めます。

これが私たちのエンティティ ”

User

“です。

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

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

    @JsonView(Views.Internal.class)
    public List<Item> 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

ビューを使用してシリアライズすると、正しく機能します。

@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<Item> userItems;
}

そしてこれが ”

CustomListSerializer

“です:

public class CustomListSerializer extends StdSerializer<List<Item>>{

   public CustomListSerializer() {
        this(null);
    }

    public CustomListSerializer(Class<List> t) {
        super(t);
    }

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

        List<Integer> 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"));
}

そして、カスタムシリアライザによるシリアライゼーションの

最終出力

:

{
 "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<Item> userItems;
}

そして、これが私たちの“

CustomListDeserializer

”です:

public class CustomListDeserializer extends StdDeserializer<List<Item>>{

    public CustomListDeserializer() {
        this(null);
    }

    public CustomListDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public List<Item> 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を使用してエンティティを双方向の関係でシリアライズ/デシリアライズする方法を説明しました。

これらすべての例とコードスニペットの実装は、https://github.com/eugenp/tutorials/tree/master/jackson#readme

にあります。

– これはMavenベースのプロジェクトです。インポートしてそのまま実行するのは簡単なはずです。