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 视图——Public和Internal其中Internal扩展Public:
public class Views {
public static class Public {}
public static class Internal extends Public {}
}
我们将在Public视图中包含所有User和Item字段——除了将包含在内部视图中的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);
}