Jackson - Decida quais campos serão serializados / desserializados
1. Visão geral
Neste artigo, exploraremos as várias maneiras de controlarif a field is serialized / deserialized by Jackson ou não.
2. Um Campo Público
A maneira mais simples de garantir que um campo seja serializável e desserializável é torná-lo público.
Vamos declarar uma classe simples com um público, um pacote privado e um privado
public class MyDtoAccessLevel {
private String stringValue;
int intValue;
protected float floatValue;
public boolean booleanValue;
// NO setters or getters
}
Dos quatro campos da classe, apenas obooleanValue público será serializado para JSON por padrão:
@Test
public void givenDifferentAccessLevels_whenPublic_thenSerializable()
throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyDtoAccessLevel dtoObject = new MyDtoAccessLevel();
String dtoAsString = mapper.writeValueAsString(dtoObject);
assertThat(dtoAsString, not(containsString("stringValue")));
assertThat(dtoAsString, not(containsString("intValue")));
assertThat(dtoAsString, not(containsString("floatValue")));
assertThat(dtoAsString, containsString("booleanValue"));
}
3. Um Getter torna um campo privado serializável e desserializável
Agora, outra maneira simples de criar um campo - especialmente um campo não-público - serializável, é adicionar um getter para ele:
public class MyDtoWithGetter {
private String stringValue;
private int intValue;
public String getStringValue() {
return stringValue;
}
}
Esperamos agora que o campostringValue seja serializável, enquanto o outro campo privado não seja, pois não tem getter:
@Test
public void givenDifferentAccessLevels_whenGetterAdded_thenSerializable()
throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyDtoGetter dtoObject = new MyDtoGetter();
String dtoAsString = mapper.writeValueAsString(dtoObject);
assertThat(dtoAsString, containsString("stringValue"));
assertThat(dtoAsString, not(containsString("intValue")));
}
De maneira não intuitiva, o getter também torna o campo privado desserializável também - porque, uma vez que ele tem um getter, o campo é considerado uma propriedade.
Vamos ver como isso funciona:
@Test
public void givenDifferentAccessLevels_whenGetterAdded_thenDeserializable()
throws JsonProcessingException, JsonMappingException, IOException {
String jsonAsString = "{\"stringValue\":\"dtoString\"}";
ObjectMapper mapper = new ObjectMapper();
MyDtoWithGetter dtoObject = mapper.readValue(jsonAsString, MyDtoWithGetter.class);
assertThat(dtoObject.getStringValue(), equalTo("dtoString"));
}
4. Um setter torna um campo não público desserializável apenas
Vimos como o getter tornava o campo privado serializável e desserializável. Por outro lado, um setter marcará apenas o campo não público como desserializável:
public class MyDtoWithSetter {
private int intValue;
public void setIntValue(int intValue) {
this.intValue = intValue;
}
public int accessIntValue() {
return intValue;
}
}
Como você pode ver, o campo privadointValue tem apenas um setter neste momento. Temos uma maneira de acessar o valor, mas não é um getter padrão.
O processo de desempacotamento paraintValue deve funcionar corretamente:
@Test
public void givenDifferentAccessLevels_whenSetterAdded_thenDeserializable()
throws JsonProcessingException, JsonMappingException, IOException {
String jsonAsString = "{\"intValue\":1}";
ObjectMapper mapper = new ObjectMapper();
MyDtoSetter dtoObject = mapper.readValue(jsonAsString, MyDtoSetter.class);
assertThat(dtoObject.anotherGetIntValue(), equalTo(1));
}
E, como mencionamos, o setter deve apenas tornar o campo desserializável, mas não serializável:
@Test
public void givenDifferentAccessLevels_whenSetterAdded_thenStillNotSerializable()
throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyDtoSetter dtoObject = new MyDtoSetter();
String dtoAsString = mapper.writeValueAsString(dtoObject);
assertThat(dtoAsString, not(containsString("intValue")));
}
5. Tornar todos os campos globalmente serializáveis
Em alguns casos em que, por exemplo, você pode não conseguir modificar o código-fonte diretamente - precisamos configurar a maneira como Jackson lida com campos não públicos de fora.
Esse tipo de configuração global pode ser feito no nível do ObjectMapper, ativando a funçãoAutoDetect para usar os campospublic ou métodos getter / setter para serialização, ou talvez ativar a serialização para todos os campos:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
O caso de teste a seguir verifica se todos os campos membros (incluindo não públicos) deMyDtoAccessLevel são serializáveis:
@Test
public void givenDifferentAccessLevels_whenSetVisibility_thenSerializable()
throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
MyDtoAccessLevel dtoObject = new MyDtoAccessLevel();
String dtoAsString = mapper.writeValueAsString(dtoObject);
assertThat(dtoAsString, containsString("stringValue"));
assertThat(dtoAsString, containsString("intValue"));
assertThat(dtoAsString, containsString("booleanValue"));
}
6. Alterar o nome de uma propriedade na serialização / desserialização
Além de controlar qual campo é serializado ou desserializado, você também podehave control over the way a fields maps to JSON and back. Eu cobrithis configuration here.
7. Ignorar um campo na serialização ou desserialização
Seguindothis tutorial, temos um guia sobre como ignorar completamente um campo na serialização e desserialização.
No entanto, às vezes precisamos apenas ignorar o campo em ambos, mas não em ambos. Jackson é flexível o suficiente para acomodar esse interessante caso de uso também.
O exemplo a seguir mostra um objetoUser que contém informações confidenciais de senha que não devem ser serializadas para JSON.
Para chegar lá, simplesmente adicionamos a anotação@JsonIgnore no getter dopassword e habilitamos a desserialização para o campo aplicando a anotação@JsonProperty no setter:
@JsonIgnore
public String getPassword() {
return password;
}
@JsonProperty
public void setPassword(String password) {
this.password = password;
}
Agora, as informações da senha não serão serializadas para JSON:
@Test
public void givenFieldTypeIsIgnoredOnlyAtSerialization_whenUserIsSerialized_thenIgnored()
throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
User userObject = new User();
userObject.setPassword("thePassword");
String userAsString = mapper.writeValueAsString(userObject);
assertThat(userAsString, not(containsString("password")));
assertThat(userAsString, not(containsString("thePassword")));
}
No entanto, o JSON que contém a senha será desserializado com êxito para o objetoUser:
@Test
public void givenFieldTypeIsIgnoredOnlyAtSerialization_whenUserIsDeserialized_thenCorrect()
throws JsonParseException, JsonMappingException, IOException {
String jsonAsString = "{\"password\":\"thePassword\"}";
ObjectMapper mapper = new ObjectMapper();
User userObject = mapper.readValue(jsonAsString, User.class);
assertThat(userObject.getPassword(), equalTo("thePassword"));
}
8. Conclusão
Este tutorial aborda o básico de como Jackson escolhewhich field is serialized/deserialized and which gets ignored no processo e, claro, como obter controle total sobre ele.
Você também pode ir para a próxima etapa para compreender Jackson 2 se aprofundando em artigos comoignoring a field,deserializing a JSON Array to a Java Array or Collection.
A implementação de todos esses exemplos e fragmentos de código pode ser encontrada emmy github project - este é um projeto baseado em Eclipse, portanto, deve ser fácil de importar e executar como está.