Использование опционально с Джексоном

Использование опционально с Джексоном

 

1. Вступление

В этой статье мы дадим обзор классаOptional, а затем объясним некоторые проблемы, с которыми мы можем столкнуться при использовании его с Джексоном.

После этого мы представим решение, которое заставит Джексона рассматриватьOptionals как обычные объекты, допускающие значение NULL.

2. Обзор проблемы

Во-первых, давайте посмотрим, что происходит, когда мы пытаемся сериализовать и десериализоватьOptionals с помощью Джексона.

2.1. Maven Dependency

Чтобы использовать Джексона, давайте убедитесь, что мы используемits latest version:


    com.fasterxml.jackson.core
    jackson-core
    2.9.6

2.2. Наша книга объект

Затем давайте создадим классBook,, содержащий одно обычное поле и одно полеOptional:

public class Book {
   String title;
   Optional subTitle;

   // getters and setters omitted
}

Имейте в виду, чтоOptionals не следует использовать в качестве полей, и мы делаем это, чтобы проиллюстрировать проблему.

2.3. Сериализация

Теперь давайте создадим экземплярBook:

Book book = new Book();
book.setTitle("Oliver Twist");
book.setSubTitle(Optional.of("The Parish Boy's Progress"));

И наконец, давайте попробуем сериализовать его, используя JacksonObjectMapper:

String result = mapper.writeValueAsString(book);

Мы увидим, что вывод поляOptional не содержит его значения, а содержит вложенный объект JSON с полем с именемpresent:

{"title":"Oliver Twist","subTitle":{"present":true}}

Хотя это может показаться странным, на самом деле это то, чего нам следует ожидать.

В этом случаеisPresent() является общедоступным геттером классаOptional. Это означает, что он будет сериализован со значениемtrue илиfalse, в зависимости от того, пуст он или нет. Это поведение сериализации Джексона по умолчанию.

Если подумать, то мы хотим, чтобы фактическое значение поляsubtitle было сериализовано.

2.4. Десериализация

Теперь давайте обратим наш предыдущий пример, на этот раз пытаясь десериализовать объект вOptional.. Мы увидим, что теперь мы получаем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 в качестве аргумента. Это не относится к нашему полюOptional.

3. Решение

Мы хотим, чтобы Джексон рассматривал пустойOptional какnull, и обрабатывал текущийOptional как поле, представляющее его значение.

К счастью, эта проблема была решена для нас. Jackson has a set of modules that deal with JDK 8 datatypes, включаяOptional.

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, мы увидим, что теперь существуетsubtitle,, а не вложенный 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,, а фактически имеем пустойOptional:

assertThat(newBook.getSubTitle()).isEqualTo(Optional.empty());

4. Заключение

Мы показали, как обойти эту проблему, используя модуль JDK 8 DataTypes, демонстрируя, как он позволяет Джексону обрабатывать пустойOptional какnull,, а текущийOptional как обычное поле. .

Реализацию этих примеров можно найтиover on GitHub; это проект на основе Maven, поэтому его легко запускать как есть.