Chiamare il serializzatore predefinito dal serializzatore personalizzato a Jackson

1. Introduzione

Serializzare la nostra struttura dati completa in JSON utilizzando un'esatta rappresentazione uno-a-uno di tutti i campi potrebbe non essere appropriato a volte o semplicemente potrebbe non essere quello che vogliamo. Invece, potremmo voler creare una visualizzazione estesa o semplificata dei nostri dati. È qui che entrano in gioco i serializzatori Jackson personalizzati.

Tuttavia, l'implementazione di un serializzatore personalizzato può essere noioso, soprattutto se i nostri oggetti modello hanno molti campi, raccolte o oggetti annidati. Fortunatamente, la biblioteca di Jackson ha diverse disposizioni che possono rendere questo lavoro molto più semplice.

In questo breve tutorial, daremo un'occhiata ai serializzatori Jackson personalizzati e mostreremo come accedere ai serializzatori predefiniti all'interno di un serializzatore personalizzato .

2. Modello di dati di esempio

Prima di immergerci nella personalizzazione di Jackson, diamo un'occhiata alla nostra classe Folder di esempio che vogliamo serializzare:

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 } 

E la classe File , che è definita come un List all'interno della nostra classe Folder :

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

3. Serializzatori personalizzati a Jackson

Il vantaggio principale dell'utilizzo di serializzatori personalizzati è che non è necessario modificare la struttura della nostra classe. Inoltre, possiamo facilmente separare il nostro comportamento previsto dalla classe stessa.

Quindi, immaginiamo di volere una visualizzazione ridotta della nostra classe Folder :

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

Come vedremo nelle prossime sezioni, ci sono diversi modi in cui possiamo ottenere il risultato desiderato a Jackson.

3.1. Approccio alla forza bruta

Innanzitutto, senza utilizzare i serializzatori predefiniti di Jackson, possiamo creare un serializzatore personalizzato in cui ci occupiamo noi stessi di tutto il lavoro pesante.

Creiamo un serializzatore personalizzato per la nostra classe Folder per ottenere questo:

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(); } }

Quindi, possiamo serializzare la nostra classe Folder in una vista ridotta contenente solo i campi che vogliamo.

3.2. Utilizzo di ObjectMapper interno

Sebbene i serializzatori personalizzati ci forniscano la flessibilità di modificare ogni proprietà in dettaglio, possiamo semplificare il nostro lavoro riutilizzando i serializzatori predefiniti di Jackson.

Un modo per utilizzare i serializzatori predefiniti è accedere alla classe ObjectMapper interna :

@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(); } 

Quindi, Jackson gestisce semplicemente il lavoro pesante serializzando gli oggetti List of File , e quindi il nostro output sarà lo stesso.

3.3. Utilizzando SerializerProvider

Un altro modo per chiamare i serializzatori predefiniti consiste nell'usare SerializerProvider. Pertanto, deleghiamo il processo al serializzatore predefinito del tipo File .

Ora, semplifichiamo un po 'il nostro codice con l'aiuto di 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(); } 

E, proprio come prima, otteniamo lo stesso output.

4. Un possibile problema di ricorsione

A seconda del caso d'uso, potrebbe essere necessario estendere i nostri dati serializzati includendo maggiori dettagli per Folder . Potrebbe trattarsi di un sistema legacy o di un'applicazione esterna da integrare che non abbiamo la possibilità di modificare .

Cambiamo il nostro serializzatore per creare un campo dettagli per i nostri dati serializzati per esporre semplicemente tutti i campi della 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(); } 

Questa volta otteniamo un'eccezione StackOverflowError .

Quando definiamo un serializzatore personalizzato, Jackson sovrascrive internamente l' istanza BeanSerializer originale creata per il tipo Folder . Di conseguenza, il nostro SerializerProvider trova ogni volta il serializzatore personalizzato, invece di quello predefinito, e questo causa un ciclo infinito.

Allora, come risolviamo questo problema? Vedremo una soluzione utilizzabile per questo scenario nella prossima sezione.

5. Utilizzo di BeanSerializerModifier

Una possibile soluzione alternativa consiste nell'usare BeanSerializerModifier per memorizzare il serializzatore predefinito per il tipo Folder prima che Jackson lo sovrascriva internamente.

Modifichiamo il nostro serializzatore e aggiungiamo un campo extra - defaultSerializer :

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

Successivamente, creeremo un'implementazione di BeanSerializerModifier per passare il serializzatore predefinito:

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; } } 

Ora, dobbiamo registrare il nostro BeanSerializerModifier come modulo per farlo funzionare:

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

Quindi, usiamo il defaultSerializer per il campo dei dettagli :

@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(); } 

Infine, potremmo voler rimuovere il campo file dai dettagli poiché lo scriviamo già separatamente nei dati serializzati.

Quindi, ignoriamo semplicemente il campo files nella nostra classe Folder :

@JsonIgnore private List files = new ArrayList(); 

Infine, il problema è risolto e otteniamo anche il nostro output atteso:

{ "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. Conclusione

In questo tutorial, abbiamo imparato come chiamare i serializzatori predefiniti all'interno di un serializzatore personalizzato nella Jackson Library.

Come sempre, tutti gli esempi di codice utilizzati in questo tutorial sono disponibili su GitHub.