Jackson中处理双向关系的最佳方法

1. 概述

在本教程中,我们将介绍在 Jackson 中处理双向关系 的最佳方法。

我们将讨论 Jackson JSON 无限递归问题,然后——我们将看到如何序列化具有双向关系的实体,最后——我们将反序列化它们。

2. 无限递归

首先——让我们来看看 Jackson 无限递归问题。 在以下示例中,我们有两个实体“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

首先,让我们用 @JsonManagedReference@JsonBackReference 注解这个关系,以便Jackson更好地处理这个关系:

User” 实体:

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

    @JsonManagedReference
    public List<Item> userItems;
}

Item“ 实体:

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

    @JsonBackReference
    public User owner;
}

现在让我们测试新的实体:

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

    final String itemJson = new ObjectMapper().writeValueAsString(item);
    final String userJson = new ObjectMapper().writeValueAsString(user);

    assertThat(itemJson, containsString("book"));
    assertThat(itemJson, not(containsString("John")));

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

下面是序列化Item对象的输出:

{
 "id":2,
 "itemName":"book"
}

下面是序列化User对象的输出:

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

请注意:

  • @JsonManagedReference 是引用的前面部分——即通常序列化的引用。
  • @JsonBackReference 是引用的后面部分——它将在序列化中被省略。
  • 序列化的Item对象不包含对User对象的引用。

另外,请注意,我们不能搞反了注解:

@JsonBackReference
public List<Item> userItems;

@JsonManagedReference
public User owner;

当我们试图反序列化对象时将抛出异常,因为*@JsonBackReference*不能在集合上使用。

如果我们想让序列化的Item对象包含对User的引用,我们需要使用*@JsonIdentityInfo*。我们将在下一节中讨论它。

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*注释来简单地忽略关系的一方,从而中断链。

在下面的例子中,我们将通过在序列化中忽略 "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* 注解来排除关系的一方。

在下面的例子中,我们使用了两个JSON视图: Public 和 Internal 其中 Internal 扩展了 Public:

public class Views {
    public static class Public {}

    public static class Internal extends Public {}
}

我们将在Public视图中包含所有UserItem字段(除了用户字段userItems),它将被包含在Internal视图中:

这是我们的实体 “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视图序列化时,它工作正常- 因为我们排除了 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. 使用自定义序列化器 @JsonSerialize

接下来,让我们看看如何使用自定义序列化器序列化具有双向关系的实体。

在下面的例子中,我们将使用自定义序列化器来序列化 "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. 使用自定义反序列化器 @JsonDeserialize

最后,让我们使用自定义反序列化器反序列化具有双向关系的实体。

在下面的例子中,我们将使用一个自定义反序列化器来解析 "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);
}

<<<<<<<<<<<< [完] >>>>>>>>>>>>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱游泳的老白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值