1. введение
В этой статье мы дадим обзор класса Optional , а затем объясним некоторые проблемы, с которыми мы можем столкнуться при его использовании с Джексоном.
После этого мы представим решение, которое заставит Джексона относиться к Необязательным так, как если бы они были обычными объектами с нулевым значением.
2. Обзор проблемы
Во-первых, давайте посмотрим, что происходит, когда мы пытаемся сериализовать и десериализовать Optionals с Джексоном.
2.1. Зависимость Maven
Чтобы использовать Джексона, давайте убедимся, что мы используем его последнюю версию :
com.fasterxml.jackson.core jackson-core 2.11.1
2.2. Объект Нашей Книги
Затем давайте создадим класс Book, содержащий одно обычное и одно Необязательное поле:
public class Book { String title; OptionalsubTitle; // getters and setters omitted }
Имейте в виду, что Необязательно не следует использовать в качестве полей, и мы делаем это, чтобы проиллюстрировать проблему.
2.3. Сериализация
Теперь давайте создадим экземпляр Книги :
Book book = new Book(); book.setTitle("Oliver Twist"); book.setSubTitle(Optional.of("The Parish Boy's Progress"));
И, наконец, давайте попробуем сериализовать его с помощью Джексона ObjectMapper :
String result = mapper.writeValueAsString(book);
Мы увидим, что выходные данные поля Optional не содержат его значения, а вместо этого представляют собой вложенный объект JSON с полем под названием present :
{"title":"Oliver Twist","subTitle":{"present":true}}
Хотя это может показаться странным, на самом деле это то, чего мы должны ожидать.
В этом случае присутствует() является общедоступным геттером в Необязательном классе. Это означает , что он будет сериализован со значением true или false , в зависимости от того, является ли он пустым или нет. Это поведение сериализации Джексона по умолчанию.
Если мы подумаем об этом, то нам нужно, чтобы фактическое значение поля subtitle было сериализовано.
2.4. Десериализация
Теперь давайте перевернем наш предыдущий пример, на этот раз пытаясь десериализовать объект в Необязательный. Мы увидим, что теперь мы получаем JsonMappingException :
@Test(expected = JsonMappingException.class) public void givenFieldWithValue_whenDeserializing_thenThrowException String bookJson = "{ \"title\": \"Oliver Twist\", \"subTitle\": \"foo\" }"; Book result = mapper.readValue(bookJson, Book.class); }
Давайте посмотрим трассировку стека:
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.util.Optional: no String-argument constructor/factory method to deserialize from String value ('The Parish Boy's Progress')
Такое поведение снова имеет смысл. По сути, Джексону нужен конструктор, который может принимать значение subtitle в качестве аргумента. Это не относится к нашему Необязательному полю.
3. Решение
Мы хотим, чтобы Джексон рассматривал пустое Необязательное как null, и рассматривал настоящее Необязательное как поле, представляющее его значение.
К счастью, эта проблема была решена для нас. У Джексона есть набор модулей , которые имеют дело с типами данных JDK8 , включая Необязательно .
3.1. Зависимость и регистрация Maven
Во-первых, давайте добавим последнюю версию в качестве зависимости Maven:
com.fasterxml.jackson.datatype jackson-datatype-jdk8 2.9.6
Теперь все, что нам нужно сделать, это зарегистрировать модуль в нашем ObjectMapper :
ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Jdk8Module());
3.2. Сериализация
А теперь давайте проверим это. Если мы снова попытаемся сериализовать наш объект Book , мы увидим, что теперь есть подзаголовок, в отличие от вложенного JSON:
Book book = new Book(); book.setTitle("Oliver Twist"); book.setSubTitle(Optional.of("The Parish Boy's Progress")); String serializedBook = mapper.writeValueAsString(book); assertThat(from(serializedBook).getString("subTitle")) .isEqualTo("The Parish Boy's Progress");
Если мы попытаемся сериализовать пустую книгу, она будет сохранена как null :
book.setSubTitle(Optional.empty()); String serializedBook = mapper.writeValueAsString(book); assertThat(from(serializedBook).getString("subTitle")).isNull();
3.3. Десериализация
Теперь давайте повторим наши тесты на десериализацию. Если мы перечитаем нашу книгу, то увидим, что мы больше не получаем исключение JsonMappingException:
Book newBook = mapper.readValue(result, Book.class); assertThat(newBook.getSubTitle()).isEqualTo(Optional.of("The Parish Boy's Progress"));
Наконец, давайте повторим тест еще раз, на этот раз с null. Мы увидим, что еще раз мы не получаем JsonMappingException, и на самом деле имеем пустой Необязательный:
assertThat(newBook.getSubTitle()).isEqualTo(Optional.empty());
4. Заключение
Мы показали, как обойти эту проблему, используя модуль типов данных JDK 8, демонстрируя, как он позволяет Джексону обрабатывать пустое Необязательное как null, и настоящее Необязательное как обычное поле.
Реализацию этих примеров можно найти на GitHub ; это проект на основе Maven, поэтому он должен быть легко запущен как есть.