Appel du sérialiseur par défaut à partir du sérialiseur personnalisé dans Jackson

1. Introduction

La sérialisation de notre structure de données complète en JSON en utilisant une représentation exacte un-à-un de tous les champs peut parfois ne pas être appropriée ou tout simplement ne pas être ce que nous voulons. Au lieu de cela, nous souhaitons peut-être créer une vue étendue ou simplifiée de nos données. C'est là que les sérialiseurs Jackson personnalisés entrent en jeu.

Cependant, l'implémentation d'un sérialiseur personnalisé peut être fastidieuse, surtout si nos objets de modèle ont beaucoup de champs, de collections ou d'objets imbriqués. Heureusement, la bibliothèque Jackson a plusieurs dispositions qui peuvent rendre ce travail beaucoup plus simple.

Dans ce court didacticiel, nous examinerons les sérialiseurs Jackson personnalisés et montrerons comment accéder aux sérialiseurs par défaut dans un sérialiseur personnalisé .

2. Exemple de modèle de données

Avant de plonger dans la personnalisation de Jackson, jetons un coup d'œil à notre exemple de classe Folder que nous voulons sérialiser:

public class Folder { private Long id; private String name; private String owner; private Date created; private Date modified; private Date lastAccess; private List files = new ArrayList(); // standard getters and setters } 

Et la classe File , qui est définie comme une liste dans notre classe Folder :

public class File { private Long id; private String name; // standard getters and setters } 

3. Sérialiseurs personnalisés à Jackson

Le principal avantage de l'utilisation de sérialiseurs personnalisés est que nous n'avons pas à modifier notre structure de classe. De plus, nous pouvons facilement dissocier notre comportement attendu de la classe elle-même.

Alors, imaginons que nous voulons une vue réduite de notre classe Folder :

{ "name": "Root Folder", "files": [ {"id": 1, "name": "File 1"}, {"id": 2, "name": "File 2"} ] } 

Comme nous le verrons dans les sections suivantes, il existe plusieurs façons d'obtenir la sortie souhaitée dans Jackson.

3.1. Approche de force brute

Premièrement, sans utiliser les sérialiseurs par défaut de Jackson, nous pouvons créer un sérialiseur personnalisé dans lequel nous faisons tout le travail nous-mêmes.

Créons un sérialiseur personnalisé pour notre classe Folder pour y parvenir:

public class FolderJsonSerializer extends StdSerializer { public FolderJsonSerializer() { super(Folder.class); } @Override public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(); gen.writeStringField("name", value.getName()); gen.writeArrayFieldStart("files"); for (File file : value.getFiles()) { gen.writeStartObject(); gen.writeNumberField("id", file.getId()); gen.writeStringField("name", file.getName()); gen.writeEndObject(); } gen.writeEndArray(); gen.writeEndObject(); } }

Ainsi, nous pouvons sérialiser notre classe Folder en une vue réduite contenant uniquement les champs souhaités.

3.2. Utilisation d' ObjectMapper interne

Bien que les sérialiseurs personnalisés nous offrent la flexibilité de modifier chaque propriété en détail, nous pouvons faciliter notre travail en réutilisant les sérialiseurs par défaut de Jackson.

Une façon d'utiliser les sérialiseurs par défaut consiste à accéder à la classe ObjectMapper interne :

@Override public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(); gen.writeStringField("name", value.getName()); ObjectMapper mapper = (ObjectMapper) gen.getCodec(); gen.writeFieldName("files"); String stringValue = mapper.writeValueAsString(value.getFiles()); gen.writeRawValue(stringValue); gen.writeEndObject(); } 

Ainsi, Jackson gère simplement le gros du travail en sérialisant la liste des objets File , puis notre sortie sera la même.

3.3. Utilisation de SerializerProvider

Une autre façon d'appeler les sérialiseurs par défaut consiste à utiliser SerializerProvider. Par conséquent, nous déléguons le processus au sérialiseur par défaut du type File .

Maintenant, simplifions un peu notre code avec l'aide de SerializerProvider :

@Override public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(); gen.writeStringField("name", value.getName()); provider.defaultSerializeField("files", value.getFiles(), gen); gen.writeEndObject(); } 

Et, comme auparavant, nous obtenons le même résultat.

4. Un problème de récursivité possible

Selon le cas d'utilisation, nous pouvons avoir besoin d'étendre nos données sérialisées en incluant plus de détails pour Folder . Cela peut être pour un système hérité ou une application externe à intégrer que nous n'avons pas la possibilité de modifier .

Modifions notre sérialiseur pour créer un champ de détails pour nos données sérialisées afin d'exposer simplement tous les champs de la classe Folder :

@Override public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(); gen.writeStringField("name", value.getName()); provider.defaultSerializeField("files", value.getFiles(), gen); // this line causes exception provider.defaultSerializeField("details", value, gen); gen.writeEndObject(); } 

Cette fois, nous obtenons une exception StackOverflowError .

Lorsque nous définissons un sérialiseur personnalisé, Jackson remplace en interne l' instance de BeanSerializer d' origine qui est créée pour le type Folder . Par conséquent, notre SerializerProvider trouve le sérialiseur personnalisé à chaque fois, au lieu de celui par défaut, et cela provoque une boucle infinie.

Alors, comment pouvons-nous résoudre ce problème? Nous verrons une solution utilisable pour ce scénario dans la section suivante.

5. Utilisation de BeanSerializerModifier

Une solution de contournement possible consiste à utiliser BeanSerializerModifier pour stocker le sérialiseur par défaut pour le type Folder avant que Jackson ne le remplace en interne.

Modifions notre sérialiseur et ajoutons un champ supplémentaire - defaultSerializer :

private final JsonSerializer defaultSerializer; public FolderJsonSerializer(JsonSerializer defaultSerializer) { super(Folder.class); this.defaultSerializer = defaultSerializer; } 

Ensuite, nous allons créer une implémentation de BeanSerializerModifier pour transmettre le sérialiseur par défaut:

public class FolderBeanSerializerModifier extends BeanSerializerModifier { @Override public JsonSerializer modifySerializer( SerializationConfig config, BeanDescription beanDesc, JsonSerializer serializer) { if (beanDesc.getBeanClass().equals(Folder.class)) { return new FolderJsonSerializer((JsonSerializer) serializer); } return serializer; } } 

Maintenant, nous devons enregistrer notre BeanSerializerModifier en tant que module pour le faire fonctionner:

ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.setSerializerModifier(new FolderBeanSerializerModifier()); mapper.registerModule(module); 

Ensuite, nous utilisons le defaultSerializer pour le champ de détails :

@Override public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(); gen.writeStringField("name", value.getName()); provider.defaultSerializeField("files", value.getFiles(), gen); gen.writeFieldName("details"); defaultSerializer.serialize(value, gen, provider); gen.writeEndObject(); } 

Enfin, nous souhaitons peut-être supprimer le champ des fichiers des détails puisque nous l'écrivons déjà séparément dans les données sérialisées.

Donc, nous ignorons simplement le champ des fichiers dans notre classe Folder :

@JsonIgnore private List files = new ArrayList(); 

Enfin, le problème est résolu et nous obtenons également notre résultat attendu:

{ "name": "Root Folder", "files": [ {"id": 1, "name": "File 1"}, {"id": 2, "name": "File 2"} ], "details": { "id":1, "name": "Root Folder", "owner": "root", "created": 1565203657164, "modified": 1565203657164, "lastAccess": 1565203657164 } } 

6. Conclusion

Dans ce didacticiel, nous avons appris à appeler des sérialiseurs par défaut dans un sérialiseur personnalisé dans Jackson Library.

Comme toujours, tous les exemples de code utilisés dans ce tutoriel sont disponibles à l'adresse over sur GitHub.