Excluir campos da serialização no Gson

Excluir campos da serialização no Gson

1. Visão geral

Neste breve tutorial, vamos explorar as opções disponíveis para excluir um ou mais campos de uma classe Java e suas subclasses da serialização Gson.

2. Configuração inicial

Vamos primeiro definir nossas classes:

@Data
@AllArgsConstructor
public class MyClass {
    private long id;
    private String name;
    private String other;
    private MySubClass subclass;
}

@Data
@AllArgsConstructor
public class MySubClass {
    private long id;
    private String description;
    private String otherVerboseInfo;
}

Nós os anotamos comLombok por conveniência (açúcar sintático para getters, setters, construtores ...).

Vamos agora preenchê-los:

MySubClass subclass = new MySubClass(42L, "the answer", "Verbose field not to serialize")
MyClass source = new MyClass(1L, "foo", "bar", subclass);

Nosso objetivo é evitar que os camposMyClass.othereMySubClass.otherVerboseInfo sejam serializados.

A saída que esperamos obter é:

{
  "id":1,
  "name":"foo",
  "subclass":{
    "id":42,
    "description":"the answer"
  }
}

Em Java:

String expectedResult = "{\"id\":1,\"name\":\"foo\",\"subclass\":{\"id\":42,\"description\":\"the answer\"}}";

3. Modificador Transiente

Podemos marcar um campo com o modificadortransient:

public class MyClass {
    private long id;
    private String name;
    private transient String other;
    private MySubClass subclass;
}

public class MySubClass {
    private long id;
    private String description;
    private transient String otherVerboseInfo;
}

O serializador do Gson ignorará todos os campos declarados como transitórios:

String jsonString = new Gson().toJson(source);
assertEquals(expectedResult, jsonString);

Embora seja muito rápido, também tem uma desvantagem severa:every serialization tool will take transient into account, não apenas Gson.

Transiente é a maneira Java de excluir da serialização, então nosso campo também será filtrado pela serialização deSerializable e por cada ferramenta de biblioteca ou estrutura que gerencia nossos objetos.

Além disso, a palavra-chavetransient sempre funciona para serialização e desserialização, o que pode ser limitante, dependendo dos casos de uso.

4. @Expose Anotação

A anotação Gsoncom.google.gson.annotations @Expose funciona ao contrário.

Podemos usá-lo para declarar quais campos serializar e ignorar os outros:

public class MyClass {
    @Expose
    private long id;
    @Expose
    private String name;
    private String other;
    @Expose
    private MySubClass subclass;
}

public class MySubClass {
    @Expose
    private long id;
    @Expose
    private String description;
    private String otherVerboseInfo;
}

Para isso, precisamos instanciar o Gson com um GsonBuilder:

Gson gson = new GsonBuilder()
  .excludeFieldsWithoutExposeAnnotation()
  .create();
String jsonString = gson.toJson(source);
assertEquals(expectedResult, jsonString);

Desta vez, podemos controlar no nível do campo se a filtragem deve ocorrer para serialização, desserialização ou ambas (padrão).

Vamos ver como evitar queMyClass.other seja serializado, mas permitindo que ele seja preenchido durante uma desserialização de JSON:

@Expose(serialize = false, deserialize = true)
private String other;

Embora esta seja a maneira mais fácil fornecida pela Gson e não afete outras bibliotecas, pode implicar em redundância no código. Se temos uma classe com uma centena de campos e queremos excluir apenas um campo, precisamos escrever uma anotação de noventa e nove, que é um exagero.

5. ExclusionStrategy

Uma solução altamente personalizável é o uso decom.google.gson.*ExclusionStrategy*.

Ele nos permite definir (externamente ou com uma Classe Interna Anônima) uma estratégia para instruir o GsonBuilder a serializar campos (e / ou classes) com critérios personalizados.

Gson gson = new GsonBuilder()
  .addSerializationExclusionStrategy(strategy)
  .create();
String jsonString = gson.toJson(source);

assertEquals(expectedResult, jsonString);

Vamos ver alguns exemplos de estratégias inteligentes para usar.

5.1. Com nomes de classes e campos

Obviamente, também podemos codificar um ou mais nomes de campos / classes:

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        if (field.getDeclaringClass() == MyClass.class && field.getName().equals("other")) {
            return true;
        }
        if (field.getDeclaringClass() == MySubClass.class && field.getName().equals("otherVerboseInfo")) {
            return true;
        }
        return false;
    }

    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }
};

Isso é rápido e direto ao ponto, mas não muito reutilizável e também propenso a erros, caso renomeamos nossos atributos.

5.2. Com critérios de negócios

Como simplesmente temos que retornar um booleano, podemos implementar todas as lógicas de negócios que gostamos dentro desse método.

No exemplo a seguir, identificaremos todos os campos que começam com "outro" como campos que não devem ser serializados, independentemente da classe a que pertencem:

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getName().startsWith("other");
    }
};

5.3. Com uma anotação personalizada

Outra abordagem inteligente é criar uma anotação personalizada:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {}

Podemos então explorarExclusionStrategy para fazê-lo funcionar exatamente como com a anotação@Expose, mas inversamente:

public class MyClass {
    private long id;
    private String name;
    @Exclude
    private String other;
    private MySubClass subclass;
}

public class MySubClass {
    private long id;
    private String description;
    @Exclude
    private String otherVerboseInfo;
}

E aqui está a estratégia:

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getAnnotation(Exclude.class) != null;
    }
};

This StackOverflow answer descreveu essa técnica pela primeira vez.

Ele nos permite escrever a anotação e a estratégia uma vez e anotar dinamicamente nossos campos sem modificações adicionais.

5.4. Estenda a estratégia de exclusão para desserialização

Não importa qual estratégia usaremos, sempre podemos controlar onde ela deve ser aplicada.

Somente durante a serialização:

Gson gson = new GsonBuilder().addSerializationExclusionStrategy(strategy)

Somente durante a desserialização:

Gson gson = new GsonBuilder().addDeserializationExclusionStrategy(strategy)

Sempre:

Gson gson = new GsonBuilder().setExclusionStrategies(strategy);

6. Conclusão

Vimos maneiras diferentes de excluir campos de uma classe e suas subclasses durante a serialização Gson.

Também exploramos as principais vantagens e armadilhas de cada solução.

Como sempre, o código-fonte completo está disponívelover on Github.