Desserializar objetos imutáveis com Jackson
1. Visão geral
Neste tutorial rápido, mostraremos duas maneiras diferentes de desserializar objetos Java imutáveis com a biblioteca de processamento JSONJackson.
2. Por que usamos objetos imutáveis?
Umimmutable object é um objeto quekeeps its state intact since the very moment of its creation. Isso significa que não importa quais métodos do objeto o usuário final chama,the object behaves the same way.
Objetos imutáveis são úteis quando projetamos umsystem that must work in a multithreaded environment, pois a imutabilidade geralmente garante a segurança do thread.
Por outro lado, objetos imutáveis são úteis quando precisamos lidar com entradas de fontes externas. Por exemplo, pode ser entrada do usuário ou alguns dados do armazenamento. Nesse caso, émay be critical to preserve the received data and protect it from accidental or unintended changes.
Vamos ver como podemos desserializar um objeto imutável.
3. Construtor público
Vamos considerar a estrutura de classeEmployee. Possui dois campos obrigatórios:idename, portanto, definimos umpublic all-arguments constructor que possui um conjunto de argumentos que corresponde ao conjunto de campos do objeto:
public class Employee {
private final long id;
private final String name;
public Employee(long id, String name) {
this.id = id;
this.name = name;
}
// getters
}
Dessa forma, teremos todos os campos do objeto inicializados no momento da criação. Final modifiers in fields’ declaration won’t let us change their values in future. Para tornar este objeto desserializável, simplesmente precisamos adicionar algunsannotations a este construtor:
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Employee(@JsonProperty("id") long id, @JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
Vamos dar uma olhada nas anotações que acabamos de adicionar.
Primeiro de tudo,@JsonCreator diz ao desserializador de Jackson parause the designated constructor for deserialization.
Existem dois modos que podem ser usados como parâmetro para esta anotação -PROPERTIESeDELEGATING.
PROPERTIES é o mais adequado quando declaramos um construtor de todos os argumentos, enquantoDELEGATING pode ser útil para construtores de um único argumento.
Depois disso,we need to annotate each of constructor arguments com@JsonProperty informando o nome da respectiva propriedade como valor de anotação. Devemos ter muito cuidado nesta etapa, pois todos osproperty names must match com os que usamos durante a serialização.
Vamos dar uma olhada em um teste de unidade simples que cobre a desserialização de um objetoEmployee:
String json = "{\"name\":\"Frank\",\"id\":5000}";
Employee employee = new ObjectMapper().readValue(json, Employee.class);
assertEquals("Frank", employee.getName());
assertEquals(5000, employee.getId());
4. Construtor Privado e Construtor
Às vezes acontece que um objeto tem um conjunto de campos opcionais. Vamos considerar outra estrutura de classe,Person, que tem um campoage opcional:
public class Person {
private final String name;
private final Integer age;
// getters
}
Quando temos um número significativo de tais campos,creating a public constructor may become cumbersome. Em outras palavras, precisaremos declarar muitos argumentos para o construtor e anotar cada um deles com anotações@JsonProperty. Como resultado, muitas declarações repetitivas tornarão nosso código inchado e difícil de ler.
Este é o caso quando umBuilder pattern clássico vem ao resgate. Vamos ver como podemos empregar seu poder na desserialização. Em primeiro lugar, vamos declarara private all-arguments constructor and a Builder class:
private Person(String name, Integer age) {
this.name = name;
this.age = age;
}
static class Builder {
String name;
Integer age;
Builder withName(String name) {
this.name = name;
return this;
}
Builder withAge(Integer age) {
this.age = age;
return this;
}
public Person build() {
return new Person(name, age);
}
}
Para fazer o desserializador Jackson usar esteBuilder, precisamos apenas adicionar duas anotações ao nosso código. Primeiro de tudo, precisamos marcar nossa classe com a anotação@JsonDeserialize, passando um parâmetrobuilder comfully qualified domain name of a builder class.
Depois disso, precisamos anotar a própria classe builder como@JsonPOJOBuilder:
@JsonDeserialize(builder = Person.Builder.class)
public class Person {
//...
@JsonPOJOBuilder
static class Builder {
//...
}
}
Observe que podemos personalizar os nomes dos métodos usados durante a compilação.
O parâmetrobuildMethodName é padronizado como “build”e representa o nome demethod that we call when the builder is ready to generate a new object.
Outro parâmetro,withPrefix, representa oprefix that we add to builder methods responsible for setting properties. O valor padrão para este parâmetro é“with”. Por isso, não especificamos nenhum desses parâmetros no exemplo.
Vamos dar uma olhada em um teste de unidade simples que cobre a desserialização de um objetoPerson:
String json = "{\"name\":\"Frank\",\"age\":50}";
Person person = new ObjectMapper().readValue(json, Person.class);
assertEquals("Frank", person.getName());
assertEquals(50, person.getAge().intValue());
5. Conclusão
Neste breve artigo, vimos como desserializar objetos imutáveis usando a biblioteca Jackson.
Todo o código relacionado a este artigo pode ser encontrado emGithub.