Chamando o serializador padrão do serializador personalizado em Jackson

Chamando o serializador padrão do serializador personalizado em Jackson

1. Introdução

Serializar nossa estrutura de dados completa para JSON usando uma representação exata de todos os campos pode não ser apropriado às vezes ou simplesmente não é o que queremos. Em vez disso,we may want to create an extended or simplified view of our data. É aqui que os serializadores Jackson personalizados entram em ação.

No entanto, a implementação de um serializador personalizado pode ser entediante, especialmente se nossos objetos de modelo tiverem muitos campos, coleções ou objetos aninhados. Felizmente, oJackson library tem várias disposições que podem tornar esse trabalho muito mais simples.

Neste breve tutorial, daremos uma olhada nos serializadores Jackson personalizados e mostraremoshow to access default serializers inside a custom serializer.

2. Modelo de Dados de Amostra

Antes de mergulharmos na personalização de Jackson, vamos dar uma olhada em nossa classeFolder de amostra que queremos serializar:

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 a classeFile, que é definida como umList dentro de nossa classeFolder:

public class File {
    private Long id;
    private String name;

    // standard getters and setters
}

3. Serializadores personalizados em Jackson

A principal vantagem do uso de serializadores personalizados é que não precisamos modificar nossa estrutura de classes. Mais,we can easily decouple our expected behavior from the class itself.

Então, vamos imaginar que queremos uma visão reduzida de nossa classeFolder:

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

Como veremos nas próximas seções, existem várias maneiras de obtermos o resultado desejado em Jackson.

3.1. Abordagem da força bruta

Primeiro, sem usar os serializadores padrão de Jackson, podemos criar um serializador personalizado em que fazemos todo o trabalho pesado sozinhos.

Vamos criar um serializador personalizado para nossa classeFolder para conseguir isso:

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

Assim, podemos serializar nossa classeFolder em uma visão reduzida contendo apenas os campos que desejamos.

3.2. UsandoObjectMapper interno

Embora os serializadores personalizados nos forneçam a flexibilidade de alterar cada propriedade em detalhes, podemos tornar nosso trabalho mais fácil emreusing Jackson’s default serializers.

Uma maneira de usar os serializadores padrão é acessar a classeObjectMapper 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();
}

Portanto, Jackson simplesmente lida com o trabalho pesado serializando os objetosList deFile, e então nossa saída será a mesma.

3.3. UsandoSerializerProvider

Outra maneira de chamar os serializadores padrão é usarSerializerProvider.. Portanto, delegamos o processo ao serializador padrão do tipoFile.

Agora, vamos simplificar nosso código um pouco com a ajuda deSerializerProvider:

@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, como antes, obtemos a mesma saída.

4. Um possível problema de recursão

Dependendo do caso de uso, podemos precisar estender nossos dados serializados, incluindo mais detalhes paraFolder. Isso pode ser pora legacy system or an external application to be integrated that we do not have a chance to modify.

Vamos mudar nosso serializador para criar um campodetails para nossos dados serializados para simplesmente expor todos os campos da classeFolder:

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

Desta vez, obtemos uma exceçãoStackOverflowError.

When we define a custom serializer, Jackson internally overrides the original BeanSerializer instance que é criado para o tipoFolder. Consequentemente, nossoSerializerProvider encontra o serializador personalizado todas as vezes, em vez do padrão, e isso causa um loop infinito.

Então, como resolvemos esse problema? Veremos uma solução utilizável para este cenário na próxima seção.

5. UsandoBeanSerializerModifier

Uma possível solução é usarBeanSerializerModifierto store the default serializer para o tipoFolderbefore Jackson internally overrides it.

Vamos modificar nosso serializador e adicionar um campo extra -defaultSerializer:

private final JsonSerializer defaultSerializer;

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


A seguir, criaremos uma implementação deBeanSerializerModifier para passar o serializador padrão:

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


Agora, precisamos registrar nossoBeanSerializerModifier como um módulo para fazê-lo funcionar:

ObjectMapper mapper = new ObjectMapper();

SimpleModule module = new SimpleModule();
module.setSerializerModifier(new FolderBeanSerializerModifier());

mapper.registerModule(module);

Em seguida, usamosdefaultSerializer para o campodetails:

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

Por último, podemos querer remover o campofiles dedetails, pois já o gravamos nos dados serializados separadamente.

Portanto, simplesmente ignoramos o campofiles em nossa classeFolder:

@JsonIgnore
private List files = new ArrayList<>();

Finalmente, o problema está resolvido e também obtemos a produção esperada:

{
    "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. Conclusão

Neste tutorial,we learned how to call default serializers inside a custom serializer in Jackson Library.

Como sempre, todos os exemplos de código usados ​​neste tutorial estão disponíveisover on GitHub.