Utilisation facultative avec Jackson

1. Introduction

Dans cet article, nous donnerons un aperçu de la classe Optional , puis expliquerons quelques problèmes que nous pourrions rencontrer lors de son utilisation avec Jackson.

Ensuite, nous présenterons une solution qui permettra à Jackson de traiter les options comme s'il s'agissait d'objets Nullables ordinaires.

2. Aperçu du problème

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

2.1. Dépendance de Maven

Pour utiliser Jackson, assurons-nous que nous utilisons sa dernière version:

 com.fasterxml.jackson.core jackson-core 2.11.1 

2.2. Notre objet de livre

Ensuite, créons un livre de classe , contenant un champ ordinaire et un champ facultatif :

public class Book { String title; Optional subTitle; // getters and setters omitted }

Gardez à l'esprit que les options ne doivent pas être utilisées comme champs et nous le faisons pour illustrer le problème.

2.3. Sérialisation

Maintenant, instancions un livre :

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 Jackson ObjectMapper :

String result = mapper.writeValueAsString(book);

Nous verrons que la sortie du champ facultatif 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 classe Optional . Cela signifie qu'il sera sérialisé avec une valeur true ou false , 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 champ de sous - titre soit sérialisée.

2.4. Désérialisation

Maintenant, inversons notre exemple précédent, en essayant cette fois de désérialiser un objet en une option. Nous verrons que maintenant nous obtenons une JsonMappingException:

@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 une fois du sens. Essentiellement, Jackson a besoin d'un constructeur qui peut prendre la valeur de sous - titre comme argument. Ce n'est pas le cas avec notre champ facultatif .

3. Solution

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

Heureusement, ce problème a été résolu pour nous. Jackson a un ensemble de modules qui traitent les types de données JDK 8, y compris facultatif .

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, tout ce que nous avons à faire est d'enregistrer le module avec notre ObjectMapper :

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

3.2. Sérialisation

Maintenant, testons-le. Si nous essayons de sérialiser à nouveau notre objet Book , nous verrons qu'il y a maintenant un sous - titre, 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é comme nul :

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 d' exception JsonMappingException:

Book newBook = mapper.readValue(result, Book.class); assertThat(newBook.getSubTitle()).isEqualTo(Optional.of("The Parish Boy's Progress"));

Enfin, répétons à nouveau le test, cette fois avec null. Nous verrons qu'une fois de plus, nous n'obtenons pas d' exception JsonMappingException, et en fait, avons un facultatif vide :

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

4. Conclusion

Nous avons montré comment contourner ce problème en exploitant le module JDK 8 DataTypes, en montrant comment il permet à Jackson de traiter un facultatif vide comme nul et un facultatif présent comme un champ ordinaire.

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