Default Serializer aanroepen vanuit Custom Serializer in Jackson

1. Inleiding

Het serialiseren van onze volledige gegevensstructuur naar JSON met behulp van een exacte een-op-een weergave van alle velden is soms niet geschikt of is simpelweg niet wat we willen. In plaats daarvan, we willen misschien een uitgebreide of vereenvoudigde weergave van onze gegevens maken. Dit is waar aangepaste Jackson-serialisatoren om de hoek komen kijken.

Het implementeren van een aangepaste serialisator kan echter vervelend zijn, vooral als onze modelobjecten veel velden, verzamelingen of geneste objecten hebben. Gelukkig heeft de Jackson-bibliotheek verschillende voorzieningen die deze klus een stuk eenvoudiger kunnen maken.

In deze korte tutorial bekijken we aangepaste Jackson-serialisatoren en laten we zien hoe u toegang krijgt tot standaardserializers in een aangepaste serialisator.

2. Voorbeeldgegevensmodel

Voordat we ingaan op het maatwerk van Jackson, laten we eerst eens kijken naar ons voorbeeld Map klasse die we willen serialiseren:

openbare klasse Map {privé Lange id; private String naam; particuliere String-eigenaar; privé Aanmaakdatum; privé Datum gewijzigd; privé Datum lastAccess; private List-bestanden = nieuwe ArrayList (); // standaard getters en setters} 

En de het dossier class, die wordt gedefinieerd als een Lijst binnen onze Map klasse:

openbare klasse Bestand {privé Lange id; private String naam; // standaard getters en setters} 

3. Aangepaste Serializers in Jackson

Het belangrijkste voordeel van het gebruik van aangepaste serializers is dat we onze klassenstructuur niet hoeven te wijzigen. Plus, we kunnen ons verwachte gedrag gemakkelijk loskoppelen van de klas zelf.

Laten we ons dus voorstellen dat we een verminderd zicht op ons willen Map klasse:

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

Zoals we in de volgende secties zullen zien, zijn er verschillende manieren waarop we de gewenste output in Jackson kunnen bereiken.

3.1. Brute Force-aanpak

Ten eerste kunnen we, zonder Jackson's standaardserializers te gebruiken, een aangepaste serialisator maken waarin we al het zware werk zelf doen.

Laten we een aangepaste serialisator maken voor onze Map klasse om dit te bereiken:

openbare klasse FolderJsonSerializer breidt StdSerializer {openbare FolderJsonSerializer () {super (Folder.class) uit; } @Override public void serialize (mapwaarde, JsonGenerator gen, SerializerProvider provider) gooit IOException {gen.writeStartObject (); gen.writeStringField ("naam", waarde.getName ()); gen.writeArrayFieldStart ("bestanden"); voor (File file: value.getFiles ()) {gen.writeStartObject (); gen.writeNumberField ("id", file.getId ()); gen.writeStringField ("naam", file.getName ()); gen.writeEndObject (); } gen.writeEndArray (); gen.writeEndObject (); }}

Zo kunnen we onze Map class naar een verkleinde weergave met alleen de velden die we willen.

3.2. Met behulp van Internal ObjectMapper

Hoewel aangepaste serialisatoren ons de flexibiliteit bieden om elke eigenschap in detail te wijzigen, kunnen we ons werk gemakkelijker maken door Hergebruik van Jackson's standaard serialisatoren.

Een manier om de standaardserializers te gebruiken, is om toegang te krijgen tot de interne ObjectMapper klasse:

@Override public void serialize (mapwaarde, JsonGenerator gen, SerializerProvider provider) gooit IOException {gen.writeStartObject (); gen.writeStringField ("naam", waarde.getName ()); ObjectMapper-mapper = (ObjectMapper) gen.getCodec (); gen.writeFieldName ("bestanden"); String stringValue = mapper.writeValueAsString (value.getFiles ()); gen.writeRawValue (stringValue); gen.writeEndObject (); } 

Jackson zorgt dus gewoon voor het zware werk door de Lijst van het dossier objecten, en dan zal onze output hetzelfde zijn.

3.3. Gebruik makend van SerializerProvider

Een andere manier om de standaardserializers aan te roepen, is door de SerializerProvider. Daarom delegeren we het proces aan de standaardserializer van het type het dossier.

Laten we nu onze code een beetje vereenvoudigen met behulp van SerializerProvider:

@Override public void serialize (mapwaarde, JsonGenerator gen, SerializerProvider provider) gooit IOException {gen.writeStartObject (); gen.writeStringField ("naam", waarde.getName ()); provider.defaultSerializeField ("bestanden", waarde.getFiles (), gen); gen.writeEndObject (); } 

En, net als voorheen, krijgen we dezelfde output.

4. Een mogelijk recursieprobleem

Afhankelijk van het gebruiksscenario moeten we mogelijk onze geserialiseerde gegevens uitbreiden met meer details voor Map. Dit kan zijn voor een legacysysteem of een te integreren externe applicatie die we niet kunnen aanpassen.

Laten we onze serialisator wijzigen om een details veld voor onze geserialiseerde gegevens om eenvoudig alle velden van de Map klasse:

@Override public void serialize (mapwaarde, JsonGenerator gen, SerializerProvider provider) gooit IOException {gen.writeStartObject (); gen.writeStringField ("naam", waarde.getName ()); provider.defaultSerializeField ("bestanden", waarde.getFiles (), gen); // deze regel veroorzaakt uitzondering provider.defaultSerializeField ("details", waarde, gen); gen.writeEndObject (); } 

Deze keer krijgen we een StackOverflowError uitzondering.

Wanneer we een aangepaste serialisator definiëren, overschrijft Jackson intern het origineel BeanSerializer voorbeeld dat is gemaakt voor het type Map. Bijgevolg is onze SerializerProvider vindt elke keer de aangepaste serialisator in plaats van de standaard, en dit veroorzaakt een oneindige lus.

Dus, hoe lossen we dit probleem op? In de volgende sectie zullen we een bruikbare oplossing voor dit scenario zien.

5. Met behulp van BeanSerializerModifier

Een mogelijke oplossing is het gebruik van BeanSerializerModifierom de standaard serialisator op te slaan voor het type Mapvoordat Jackson het intern opheft.

Laten we onze serialisator aanpassen en een extra veld toevoegen - defaultSerializer:

private finale JsonSerializer defaultSerializer; openbare FolderJsonSerializer (JsonSerializer defaultSerializer) {super (Folder.class); this.defaultSerializer = defaultSerializer; } 

Vervolgens maken we een implementatie van BeanSerializerModifier om de standaard serialisator te halen:

public class FolderBeanSerializerModifier breidt BeanSerializerModifier uit {@Override public JsonSerializer adjustSerializer (SerializationConfig config, BeanDescription beanDesc, JsonSerializer serializer) {if (beanDesc.getBeanClass (). equals (FolderS serializer (). equals (FolderS serializer) (). } return serialisator; }} 

Nu moeten we onze BeanSerializerModifier als module om het te laten werken:

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

Vervolgens gebruiken we de defaultSerializer voor de details veld:

@Override public void serialize (mapwaarde, JsonGenerator gen, SerializerProvider provider) gooit IOException {gen.writeStartObject (); gen.writeStringField ("naam", waarde.getName ()); provider.defaultSerializeField ("bestanden", waarde.getFiles (), gen); gen.writeFieldName ("details"); defaultSerializer.serialize (waarde, gen, provider); gen.writeEndObject (); } 

Ten slotte willen we misschien de bestanden veld uit de details aangezien we het al afzonderlijk in de geserialiseerde gegevens schrijven.

Dus we negeren gewoon de bestanden veld in ons Map klasse:

@JsonIgnore privélijstbestanden = nieuwe ArrayList (); 

Eindelijk is het probleem opgelost en krijgen we ook onze verwachte output:

{"naam": "Hoofdmap", "bestanden": [{"id": 1, "naam": "Bestand 1"}, {"id": 2, "naam": "Bestand 2"}], "details": {"id": 1, "name": "Hoofdmap", "owner": "root", "created": 1565203657164, "modified": 1565203657164, "lastAccess": 1565203657164}} 

6. Conclusie

In deze tutorial we hebben geleerd hoe we standaardserializers kunnen aanroepen in een aangepaste serialisator in Jackson Library.

Zoals altijd zijn alle codevoorbeelden die in deze tutorial worden gebruikt, beschikbaar op GitHub.