Utilisation de Optional avec Jackson

Utilisation de facultatif avec Jackson

 

1. introduction

Dans cet article, nous allons donner un aperçu de la classeOptional, puis expliquer certains problèmes que nous pourrions rencontrer lors de son utilisation avec Jackson.

Ensuite, nous allons introduire une solution qui permettra à Jackson de traiter lesOptionals comme s’il s’agissait d’objets Nullables ordinaires.

2. Aperçu du problème

Tout d'abord, voyons ce qui se passe lorsque nous essayons de sérialiser et de désérialiserOptionals avec Jackson.

2.1. Dépendance Maven

Pour utiliser Jackson, vérifions que nous utilisonsits latest version:


    com.fasterxml.jackson.core
    jackson-core
    2.9.6

2.2. Notre livre objet

Ensuite, créons une classeBook, contenant un champ ordinaire et un champOptional:

public class Book {
   String title;
   Optional subTitle;

   // getters and setters omitted
}

Gardez à l'esprit queOptionals ne doit pas être utilisé comme champs et nous faisons cela pour illustrer le problème.

2.3. La sérialisation

Maintenant, instancions unBook:

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

Et enfin, essayons de le sérialiser à l'aide d'un JacksonObjectMapper:

String result = mapper.writeValueAsString(book);

Nous verrons que la sortie du champOptional, ne contient pas sa valeur, mais plutôt un objet JSON imbriqué avec un champ appelépresent:

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

Bien que cela puisse paraître étrange, c'est en fait ce à quoi nous devons nous attendre.

Dans ce cas,isPresent() est un getter public sur la classeOptional. Cela signifie qu'il sera sérialisé avec une valeur detrue oufalse, selon qu'il est vide ou non. Il s'agit du comportement de sérialisation par défaut de Jackson.

Si nous y réfléchissons, ce que nous voulons, c'est que la valeur réelle du champsubtitle soit sérialisée.

2.4. Désérialisation

Maintenant, inversons notre exemple précédent, cette fois en essayant de désérialiser un objet en unOptional. Nous allons voir que maintenant nous obtenons unJsonMappingException:

@Test(expected = JsonMappingException.class)
public void givenFieldWithValue_whenDeserializing_thenThrowException
    String bookJson = "{ \"title\": \"Oliver Twist\", \"subTitle\": \"foo\" }";
    Book result = mapper.readValue(bookJson, Book.class);
}

Voyons la trace de la pile:

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')

Ce comportement a encore un sens. Essentiellement, Jackson a besoin d'un constructeur qui peut prendre la valeur desubtitle comme argument. Ce n'est pas le cas avec notre champOptional.

3. Solution

Ce que nous voulons, c'est que Jackson traite unOptional vide commenull, et traite unOptional présent comme un champ représentant sa valeur.

Heureusement, ce problème a été résolu pour nous. Jackson has a set of modules that deal with JDK 8 datatypes, y comprisOptional.

3.1. Dépendance et enregistrement Maven

Tout d'abord, ajoutons la dernière version en tant que dépendance Maven:


   com.fasterxml.jackson.datatype
   jackson-datatype-jdk8
   2.9.6

Maintenant, il ne nous reste plus qu'à enregistrer le module avec nosObjectMapper:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());

3.2. La sérialisation

Maintenant, testons-le. Si nous essayons de sérialiser à nouveau notre objetBook, nous verrons qu'il existe maintenant unsubtitle, par opposition à un JSON imbriqué:

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");

Si nous essayons de sérialiser un livre vide, il sera stocké en tant quenull:

book.setSubTitle(Optional.empty());
String serializedBook = mapper.writeValueAsString(book);

assertThat(from(serializedBook).getString("subTitle")).isNull();

3.3. Désérialisation

Maintenant, répétons nos tests de désérialisation. Si nous relisons notre livre, nous verrons que nous n’obtenons plus deJsonMappingException:

Book newBook = mapper.readValue(result, Book.class);

assertThat(newBook.getSubTitle()).isEqualTo(Optional.of("The Parish Boy's Progress"));

Enfin, répétons le test, cette fois avecnull. Nous verrons qu’une fois de plus nous n’obtenons pas deJsonMappingException, et en fait, nous avons unOptional: vide

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

4. Conclusion

Nous avons montré comment contourner ce problème en tirant parti du module JDK 8 DataTypes, en démontrant comment il permet à Jackson de traiter unOptional vide commenull, et un présentOptional comme un champ ordinaire .

L'implémentation de ces exemples peut être trouvéeover on GitHub; il s'agit d'un projet basé sur Maven, il devrait donc être facile à exécuter tel quel.