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. 使用 ,
首先,讓我們用 和 註解關係,以便 Jackson 更好地處理關係:以下是“”實體:
public class User {
public int id;
public String name;
@JsonManagedReference
public List<Item> userItems;
}
以及“”:
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"}]
}
請注意:
- 是引用的一部分(forward part),它會被正常序列化。
- 是引用的一部分(back part),它不會包含在序列化中。
- 序列化的 對象不包含對 對象的引用。
請注意,我們不能交換註解。以下代碼對於序列化有效:
@JsonBackReference
public List<Item> userItems;
@JsonManagedReference
public User owner;
但是,當我們嘗試反序列化對象時,它會拋出異常,因為 不能用於集合。
如果我們想讓序列化的 對象包含對 對象的引用,則需要使用 。我們將會在下一部分中討論它。
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"
}
}
我們還可以使用較新的註解來排除關係的其中一側。
在下面的示例中,我們將使用兩個JSON視圖,和,其中繼承:
public class Views {
public static class Public {}
public static class Internal extends Public {}
}
我們將包含所有和字段在視圖中,除了字段,該字段將在視圖中包含:
以下是我們的實體:
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;
}
以下是我們的“”實體:
public class Item {
@JsonView(Views.Public.class)
public int id;
@JsonView(Views.Public.class)
public String itemName;
@JsonView(Views.Public.class)
public User owner;
}
當我們使用視圖進行序列化時,它會正常工作,因為我們排除了不被序列化:
@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")));
}
但是,如果我們使用視圖進行序列化,則會拋出,因為所有字段都包含在內:
@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
” property “userItems“.”">在下面的示例中,我們將使用自定義序列化器來序列化“User”屬性“userItems”。User
” entity:">以下是“User”實體:public class User {
public int id;
public String name;
@JsonSerialize(using = CustomListSerializer.class)
public List<Item> userItems;
}
CustomListSerializer“:">以下是“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);
}
10. 解決 Jackson 異常
當我們同時在同一個實體中使用多重雙向關係時,可能會在序列化過程中遇到異常:
com.fasterxml.jackson.databind.JsonMappingException: Multiple back-reference properties with name 'defaultReference'
這是因為 Jackson 將默認引用名稱 ‘defaultReference‘ 賦給每個 @JsonBackReference,除非我們明確提供值。 如果一個實體具有多個引用且未指定唯一名稱,Jackson 不知道如何區分它們。
假設我們有一個 User 包含兩個項目列表:wishlist 和 soldItems。兩者都引用 User 作為所有者:
public class User {
public int id;
public String name;
@JsonManagedReference
public List wishlist = new ArrayList<>();
@JsonManagedReference
public List soldItems = new ArrayList<>();
}
public class Item {
public int id;
public String itemName;
@JsonBackReference
public User owner;
}
這將導致 Jackson 在序列化期間拋出 JsonMappingException,因為它無法區分具有相同默認名稱的多個引用。
要解決此問題,我們需要為每對引用提供一個唯一的名稱,通過 value 屬性:
public class User {
public int id;
public String name;
@JsonManagedReference(value="wishlistRef")
public List wishlist = new ArrayList<>();
@JsonManagedReference(value="soldItemsRef")
public List soldItems = new ArrayList<>();
}
public class Item {
public int id;
public String itemName;
@JsonBackReference(value="wishlistRef")
public User wishlistOwner;
@JsonBackReference(value="soldItemsRef")
public User soldOwner;
}
現在,Jackson 知道哪個引用與哪個管理引用相對應,序列化工作正常。
我們可以編寫一個測試示例,演示如何使用命名的 @JsonManagedReference 和 @JsonBackReference 註解允許 Jackson 正確地序列化具有多個引用對象,而不會拋出異常:
@Test
public void givenMultipleBackReferencesOnWishlist_whenNamedReference_thenNoException() throws JsonProcessingException {
User user = new User();
user.id = 1;
user.name = "Alice";
Item item1 = new Item();
item1.id = 101;
item1.itemName = "Book";
item1.wishlistOwner = user;
Item item2 = new Item();
item2.id = 102;
item2.itemName = "Pen";
item2.wishlistOwner = user;
user.wishlist = List.of(item1, item2);
Item item3 = new Item();
item3.id = 201;
item3.itemName = "Laptop";
item3.soldOwner = user;
Item item4 = new Item();
item4.id = 202;
item4.itemName = "Phone";
item4.soldOwner = user;
user.soldItems = List.of(item3, item4);
String json = new ObjectMapper().writeValueAsString(user);
assertThat(json, containsString("Alice"));
assertThat(json, containsString("Book"));
assertThat(json, containsString("Pen"));
}
11. 結論
在本文中,我們演示瞭如何使用 Jackson 通過雙向關係序列化/反序列化實體。