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.