Contents

Jackson 序列化/反序列化具有双向关系

1. 概述

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

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

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: 
com.blogdemo.jackson.bidirection.Item["owner"]
->com.blogdemo.jackson.bidirection.User["userItems"]
->java.util.ArrayList[0]
->com.blogdemo.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 视图——PublicInternal其中Internal扩展Public

public class Views {
    public static class Public {}
    public static class Internal extends Public {}
}

我们将在Public视图中包含所有UserItem字段——除了将包含在内部视图中的User字段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<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. 使用自定义序列化器

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

在下面的示例中——我们将使用自定义序列化程序来序列化“ 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);
}