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


    1. 概述

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

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

    2. 无限递归

    首先——让我们来看看 Jackson 无限递归问题。 在以下示例中,我们有两个实体“User”和“Item”具有简单的一对多关系

    User” 实体:

    public class User {
        public int id;
        public String name;
        public List<Item> userItems;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Item” 实体:

    public class Item {
        public int id;
        public String itemName;
        public User owner;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当我们试图序列化“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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    完整异常是:

    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"]
    ->..
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    让我们看看,在接下来的几节中,如何解决这个问题。

    3. 使用 @JsonManagedReference, @JsonBackReference

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

    User” 实体:

    public class User {
        public int id;
        public String name;
    
        @JsonManagedReference
        public List<Item> userItems;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Item“ 实体:

    public class Item {
        public int id;
        public String itemName;
    
        @JsonBackReference
        public User owner;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    @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"));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    {
     "id":2,
     "itemName":"book"
    }
    
    • 1
    • 2
    • 3
    • 4

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

    {
     "id":1,
     "name":"John",
     "userItems":[{
       "id":2,
       "itemName":"book"}]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    请注意:

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

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

    @JsonBackReference
    public List<Item> userItems;
    
    @JsonManagedReference
    public User owner;
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

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

    4. 使用 @JsonIdentityInfo

    现在,让我们看看如何使用*@JsonIdentityInfo*帮助序列化具有双向关系的实体。

    我们将类级注释添加到 “User” 实体:

    @JsonIdentityInfo(
      generator = ObjectIdGenerators.PropertyGenerator.class,
      property = "id")
    public class User { ... }
    
    • 1
    • 2
    • 3
    • 4

    以及 “Item” 实体:

    @JsonIdentityInfo(
      generator = ObjectIdGenerators.PropertyGenerator.class,
      property = "id")
    public class Item { ... }
    
    • 1
    • 2
    • 3
    • 4

    测试一下:

    @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"));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    下面是序列化的输出:

    {
     "id":2,
     "itemName":"book",
     "owner":
        {
            "id":1,
            "name":"John",
            "userItems":[2]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5. 使用 @JsonIgnore

    或者,我们还可以使用*@JsonIgnore*注释来简单地忽略关系的一方,从而中断链。

    在下面的例子中,我们将通过在序列化中忽略 "User " 属性 "userItems " 来防止无限递归:

    User” 实体:

    public class User {
        public int id;
        public String name;
    
        @JsonIgnore
        public List<Item> userItems;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    下面是我们的测试:

    @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")));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    下面是序列化的输出:

    {
     "id":2,
     "itemName":"book",
     "owner":
        {
            "id":1,
            "name":"John"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    6. 使用 @JsonView

    我们还可以使用较新的*@JsonView* 注解来排除关系的一方。

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

    public class Views {
        public static class Public {}
    
        public static class Internal extends Public {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们将在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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这是我们的实体 “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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当我们使用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")));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    但是如果我们使用 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    7. 使用自定义序列化器 @JsonSerialize

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

    在下面的例子中,我们将使用自定义序列化器来序列化 "User " 属性 "userItems ":

    User” 实体:

    public class User {
        public int id;
        public String name;
    
        @JsonSerialize(using = CustomListSerializer.class)
        public List<Item> userItems;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里是自定义序列化器: 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    现在让我们测试一下序列化器,看看生成的输出类型是否正确:

    @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"));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    使用自定义序列化器的序列化的最终输出:

    {
     "id":2,
     "itemName":"book",
     "owner":
        {
            "id":1,
            "name":"John",
            "userItems":[2]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    8. 反序列化用 @JsonIdentityInfo

    现在,让我们看看如何使用 @JsonIdentityInfo 来反序列化具有双向关系的实体。

    User” 实体:

    @JsonIdentityInfo(
      generator = ObjectIdGenerators.PropertyGenerator.class,
      property = "id")
    public class User { ... }
    
    • 1
    • 2
    • 3
    • 4

    Item” 实体:

    @JsonIdentityInfo(
      generator = ObjectIdGenerators.PropertyGenerator.class,
      property = "id")
    public class Item { ... }
    
    • 1
    • 2
    • 3
    • 4

    现在让我们编写一个快速测试,从一些我们想要解析的手动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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    9. 使用自定义反序列化器 @JsonDeserialize

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

    在下面的例子中,我们将使用一个自定义反序列化器来解析 "User " 属性 "userItems ":

    User” 实体:

    public class User {
        public int id;
        public String name;
    
        @JsonDeserialize(using = CustomListDeserializer.class)
        public List<Item> userItems;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    自定义的反序列化器: 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<>();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    还有一个简单的测试:

    @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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

  • 相关阅读:
    PS画布基本操作 新建保存 图片类型区分设置
    无穷逻辑介绍
    Python编程 字节
    通讯录管理系统-C++课程设计
    hive sql 取当周周一 str_to_date(DATE_FORMAT(biz_date, ‘%Y%v‘), ‘%Y%v‘)
    走进乌镇峰会,《个人信息保护法》实施一周年实践与展望
    目标检测YOLO实战应用案例100讲-基于YOLOv7的番茄采摘机械手场景感知及试验(中)
    在VScode中如何将界面语言设置为中文
    【day10.01】使用select实现服务器并发
    [附源码]java毕业设计小区物业管理系统论文
  • 原文地址:https://blog.csdn.net/wjw465150/article/details/127745652