Aufrufen des Standard-Serializers vom benutzerdefinierten Serializer in Jackson

1. Einleitung

Die Serialisierung unserer vollständigen Datenstruktur in JSON unter Verwendung einer exakten Eins-zu-Eins-Darstellung aller Felder ist manchmal nicht angemessen oder einfach nicht das, was wir wollen. Stattdessen möchten wir möglicherweise eine erweiterte oder vereinfachte Ansicht unserer Daten erstellen. Hier kommen benutzerdefinierte Jackson-Serialisierer ins Spiel.

Das Implementieren eines benutzerdefinierten Serializers kann jedoch mühsam sein, insbesondere wenn unsere Modellobjekte viele Felder, Sammlungen oder verschachtelte Objekte enthalten. Glücklicherweise verfügt die Jackson-Bibliothek über mehrere Bestimmungen, die diese Arbeit erheblich vereinfachen können.

In diesem kurzen Tutorial werfen wir einen Blick auf benutzerdefinierte Jackson-Serialisierer und zeigen, wie Sie auf Standardserialisierer in einem benutzerdefinierten Serializer zugreifen können .

2. Beispieldatenmodell

Bevor wir in die Anpassung von Jackson tauchen, lassen Sie uns einen Blick auf unsere Probe haben Ordner Klasse , dass wir serialisieren möchten:

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 } 

Und die File- Klasse, die in unserer Folder- Klasse als Liste definiert ist :

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

3. Benutzerdefinierte Serializer in Jackson

Der Hauptvorteil der Verwendung von benutzerdefinierten Serialisierern besteht darin, dass wir unsere Klassenstruktur nicht ändern müssen. Außerdem können wir unser erwartetes Verhalten leicht von der Klasse selbst entkoppeln.

Stellen wir uns also vor, wir möchten eine verkleinerte Ansicht unserer Ordnerklasse :

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

Wie wir in den nächsten Abschnitten sehen werden, gibt es verschiedene Möglichkeiten, wie wir in Jackson unsere gewünschte Leistung erzielen können.

3.1. Brute-Force-Ansatz

Erstens können wir ohne die Verwendung von Jacksons Standard-Serialisierern einen benutzerdefinierten Serializer erstellen, in dem wir das ganze schwere Heben selbst erledigen.

Erstellen wir einen benutzerdefinierten Serializer für unsere Ordnerklasse , um dies zu erreichen:

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

Auf diese Weise können wir unsere Ordnerklasse in eine reduzierte Ansicht serialisieren, die nur die gewünschten Felder enthält.

3.2. Verwenden des internen ObjectMapper

Obwohl benutzerdefinierte Serialisierer uns die Flexibilität bieten, jede Eigenschaft im Detail zu ändern, können wir unsere Arbeit erleichtern, indem wir Jacksons Standard-Serialisierer wiederverwenden.

Eine Möglichkeit, die Standard-Serializer zu verwenden, besteht darin, auf die interne ObjectMapper- Klasse zuzugreifen :

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

Also erledigt Jackson einfach das schwere Heben, indem er die Liste der Dateiobjekte serialisiert , und dann wird unsere Ausgabe dieselbe sein.

3.3. Verwenden von SerializerProvider

Eine andere Möglichkeit, die Standard-Serializer aufzurufen, ist die Verwendung des SerializerProvider. Daher delegieren wir den Prozess an den Standard-Serializer vom Typ Datei .

Lassen Sie uns nun unseren Code mit Hilfe von SerializerProvider ein wenig vereinfachen :

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

Und nach wie vor erhalten wir die gleiche Ausgabe.

4. Ein mögliches Rekursionsproblem

Je nach Anwendungsfall müssen wir unsere serialisierten Daten möglicherweise um weitere Details für den Ordner erweitern . Dies kann dazu führen, dass ein Altsystem oder eine externe Anwendung integriert wird, die wir nicht ändern können .

Lassen Sie uns unseren Serializer ändern, um ein Detailfeld für unsere serialisierten Daten zu erstellen, in dem einfach alle Felder der Folder- Klasse verfügbar gemacht werden :

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

Diesmal erhalten wir eine StackOverflowError- Ausnahme.

Wenn wir einen benutzerdefinierten Serializer definieren, überschreibt Jackson intern die ursprüngliche BeanSerializer- Instanz , die für den Typ Folder erstellt wurde . Infolgedessen findet unser SerializerProvider jedes Mal den angepassten Serializer anstelle des Standard-Serialisierers, was zu einer Endlosschleife führt.

Wie lösen wir dieses Problem? Wir werden im nächsten Abschnitt eine brauchbare Lösung für dieses Szenario sehen.

5. Verwenden von BeanSerializerModifier

Eine mögliche Problemumgehung besteht darin, BeanSerializerModifier zu verwenden, um den Standard-Serializer für den Typ Folder zu speichern, bevor Jackson ihn intern überschreibt.

Lassen Sie uns unseren Serializer ändern und ein zusätzliches Feld hinzufügen - defaultSerializer :

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

Als Nächstes erstellen wir eine Implementierung von BeanSerializerModifier , um den Standard-Serializer zu übergeben:

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

Jetzt müssen wir unseren BeanSerializerModifier als Modul registrieren , damit er funktioniert:

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

Dann verwenden wir die defaultSerializer für die Details Feld:

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

Zuletzt möchten wir möglicherweise das Dateifeld aus den Details entfernen, da wir es bereits separat in die serialisierten Daten schreiben.

Daher ignorieren wir einfach das Dateifeld in unserer Ordnerklasse :

@JsonIgnore private List files = new ArrayList(); 

Schließlich ist das Problem gelöst und wir erhalten auch unsere erwartete Leistung:

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

In diesem Tutorial haben wir gelernt, wie Sie Standard-Serializer in einem benutzerdefinierten Serializer in der Jackson Library aufrufen.

Wie immer sind alle in diesem Tutorial verwendeten Codebeispiele auf GitHub verfügbar.