Автор оригинала: Eugen Paraschiv.
1. Обзор
В этом уроке мы рассмотрим лучшие способы работы с двунаправленными отношениями в Jackson .
Мы обсудим проблему бесконечной рекурсии Джексона JSON, затем – мы увидим, как сериализовать сущности с двунаправленными отношениями, и, наконец, – мы десериализуем их.
2. Бесконечная Рекурсия
Во – первых, давайте взглянем на проблему бесконечной рекурсии Джексона. В следующем примере у нас есть две сущности – ” Пользователь ” и ” Элемент “– с простым отношением “один ко многим” :
Сущность ” Пользователь “:
public class User {
public int id;
public String name;
public List- userItems;
}
В ” Элемент ” сущность:
public class Item {
public int id;
public String itemName;
public User owner;
}Когда мы пытаемся сериализовать экземпляр ” Item “, Джексон выдаст 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 , чтобы Джексон лучше справлялся с отношением:
Вот сущность ” User “:
public class User {
public int id;
public String name;
@JsonManagedReference
public List- 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"
}А вот результат сериализации пользовательского объекта:
{
"id":1,
"name":"John",
"userItems":[{
"id":2,
"itemName":"book"}]
}Обратите внимание, что:
- @JsonManagedReference – это прямая часть ссылки, которая обычно сериализуется.
- @JsonBackReference является обратной частью ссылки – она будет опущена из сериализации.
- Сериализованный Элемент объект не содержит ссылки на объект User .
Кроме того, обратите внимание, что мы не можем переключаться между аннотациями. Для сериализации будет работать следующее:
@JsonBackReference public List- userItems; @JsonManagedReference public User owner;
Но вызовет исключение, когда мы попытаемся десериализовать объект, так как @JsonBackReference не может использоваться в коллекции.
Если мы хотим, чтобы объект сериализованного элемента содержал ссылку на пользователя, нам нужно использовать @JsonIdentityInfo . Мы рассмотрим это в следующем разделе.
4. Используйте @JsonIdentityInfo
Теперь давайте посмотрим, как помочь с сериализацией сущностей с двунаправленными отношениями с помощью @JsonIdentityInfo .
Мы добавляем аннотацию уровня класса к нашей сущности ” User “:
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class User { ... }И к ” Элемент ” сущность:
@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 ” от сериализации:
Вот ” Пользователь ” сущность:
public class User {
public int id;
public String name;
@JsonIgnore
public List- 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 {}
}Мы включим все User и Item поля в Public View – за исключением User поля userItems , которые будут включены в Внутреннее представление:
Вот наша сущность ” Пользователь “:
public class User {
@JsonView(Views.Public.class)
public int id;
@JsonView(Views.Public.class)
public String name;
@JsonView(Views.Internal.class)
public List- 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;
}Когда мы сериализуем с помощью представления 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")));
}Но если мы сериализуем с помощью внутреннего представления, 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- userItems;
}
А вот ” Пользовательский ListSerializer “:
public class CustomListSerializer extends StdSerializer>{ public CustomListSerializer() { this(null); } public CustomListSerializer(Class
t) { super(t); } @Override public void serialize( List
- items, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException { List
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 { ... }И ” Элемент ” сущность:
@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 “:
Вот ” Пользователь ” сущность:
public class User {
public int id;
public String name;
@JsonDeserialize(using = CustomListDeserializer.class)
public List- userItems;
}
А вот наш ” Пользовательский десериализатор списка “:
public class CustomListDeserializer extends StdDeserializer>{ public CustomListDeserializer() { this(null); } public CustomListDeserializer(Class> vc) { super(vc); } @Override public List
- 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. Заключение
В этом руководстве мы проиллюстрировали, как сериализовать/десериализовать сущности с двунаправленными отношениями с помощью Джексона.
Реализацию всех этих примеров и фрагментов кода можно найти в нашем проекте GitHub – это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.