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视图中包含所有User和Item字段(除了用户字段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);
}
<<<<<<<<<<<< [完] >>>>>>>>>>>>