Olá, pessoal. Tudo bem?
Nesse artigo, vamos criar duas anotações em Java que nos ajudarão a transformar um objeto em JSON, aquele famoso formato de texto utilizado na comunicação de aplicações.
Para isso, vamos começar introduzindo os assuntos “anotação” e “reflection” e depois partiremos pro projeto. Vamos lá?
O que é uma anotação?
Primeiramente, vamos entender o que são anotações.
As anotações fornecem metadados para o nosso código e não afetam diretamente a execução do mesmo, apesar de poderem ser usadas com essa função. Assim, podemos anotar classes, objetos, parâmetros e métodos.
Se você já está no mundo do Java há um tempo, já viu algo assim:
**@Deprecated**
código {
código
}
Isso nada mais é que um trecho de código anotado, cada anotação significando alguma coisa. Nesse caso, por exemplo, é apenas informação para o compilador informando que o método atual está depreciado e não mais deveria ser utilizado.
Além de anotações para tempo de compilação, temos anotações para build e tempo de execução. Iremos utilizar o último tipo em nosso projeto.
Reflection
E o Reflection? Ele é uma interface interface Java, que permite que nosso código olhe para dentro de si mesmo. Por exemplo: uma classe pode obter o nome dos próprios atributos ou ver se possui alguma anotação, o que torna essa interface interessante para nós.
Agora, vamos utilizar essa ferramenta para informar ao código o que queremos que aconteça com a classe e seus membros anotados.
Criando anotações
Já que anotações são interfaces, vamos criar uma para anotar a classe e outra para anotar seus atributos, que posteriormente serão convertidos para o formato JSON.
Anotação de Classe
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JsonClasse {
}
Anotação de Atributos(elementos)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonElemento {
public String key() default "";
}
Obs: sim, para criar anotações, precisamos de… anotações!
- @Retention: com essa, definimos até onde a anotação vai ser retida. No nosso caso, como já foi dito, será até o Runtime(tempo de execução).
- @Target: define o campo que ela irá anotar: para classe, é o TYPE(classes são tipos); para atributos, FIELD(campos, ou seja, atributos).
- Com a String key, definimos que o que for anotado terá uma chave, com o padrão sendo uma string vazia.
Mas não se preocupe, entenderemos isso melhor mais adiante!
Criando a classe Pessoa
@JsonClasse
public class Pessoa {
@JsonElemento()
private String nome;
@JsonElemento()
private String sobrenome;
@JsonElemento(key = "pessoaIdade")
private String idade;
public Pessoa(String nome, String sobrenome, String idade) {
this.nome = nome;
this.sobrenome = sobrenome;
this.idade = idade;
}
public Pessoa() {
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getSobrenome() {
return sobrenome;
}
public void setSobrenome(String sobrenome) {
this.sobrenome = sobrenome;
}
public String getIdade() {
return idade;
}
public void setIdade(String idade) {
this.idade = idade;
}
}
Essa, como você pode perceber, é uma classe comum onde a Pessoa possui nome, sobrenome e idade. Anotamos a classe com @JsonClasse; os atributos, com @JsonElemento. Deixemos os valores do key para depois.
Implementação do projeto
Agora que temos nossas anotações e uma classe para usar no exemplo, vamos implementar a classe que será responsável por converter um objeto para um JSON:
public class ConversorObjetoToJson {
}
Método convertToJson
Esse é o método que iremos chamar para realizar a conversão:
public String convertToJson(Object objeto) {
try {
checaSeEhPossivelConverter(objeto);
return getJson(objeto);
} catch ( IllegalAccessException e) {
throw new RuntimeException("xiiiii" + e.getMessage());
}
}
Esse método recebe um objeto do tipo Object; sim, estamos falando daquela classe que dá origem a tudo no Java. O que esse método fará é checar se é possível fazer a conversão através do método checaSeEhPossivelConverter que recebe o objeto e, se possível, posteriormente chama o getJson que também recebe o objeto.
Método checaSeEhPossivelConverter
private void checaSeEhPossivelConverter(Object object) {
if(Objects.isNull(object)) {
throw new RuntimeException("Objeto nulo");
}
Class<?> classe = object.getClass();
if(!classe.isAnnotationPresent(JsonClasse.class)) {
throw new RuntimeException("A classe nao esta anotada");
}
}
Portanto, esse método faz a checagem do objeto para saber se ele é nulo ou se não está anotado. Se alguma condição for verdadeira, uma exceção é lançada.
E não esqueça de perceber que a reflection é utilizada com um objeto do tipo Class e o método isAnnotationPresent.
Método getJson
Preste atenção para evitar ficar confuso: utilizaremos bastante reflection para converter o objeto para uma string no formato de JSON.
private String getJson(Object object) throws IllegalAccessException {
Class<?> classe = object.getClass();
Map<String, String> jsonElementsMap = new HashMap<>();
for(Field field : classe.getDeclaredFields()) {
field.setAccessible(true);
if(field.isAnnotationPresent(JsonElemento.class)) {
jsonElementsMap.put(getKey(field), (String) field.get(object));
}
}
String jsonString = jsonElementsMap.entrySet()
.stream()
.map(entry -> " \\"" + entry.getKey() + "\\":\\""
+ entry.getValue() + "\\"")
.collect(Collectors.joining(",\\n"));
return "{\\n" + jsonString + "\\n}";
}
Agora, vamos analisar linha a linha:
- Primeiramente usamos um objeto do tipo Class para armazenar o retorno de getClass() do nosso objeto, que retorna a classe dele em tempo de execução. Reflection pura.
- Depois, criamos um HashMap, com chave e valor do tipo String onde estarão as informações do JSON.
- Em seguida, vamos utilizar outra classe de Reflection nesse for, o Field. Para cada field da classe(objeto da linha 1), pegue os campos declarados(getDeclaredFields()).
- Precisamos usar esse setAccessible, pois declaramos os campos como private.
- Agora, se o field tiver a anotação JsonElemento, colocamos uma chave e um valor no HashMap. Obteremos a chave com outro método, o getKey(), e o valor com o field.get que recebe o objeto passado.
- Depois, já com todas as informações no HashMap, vamos transformá-lo em String utilizando o .entrySet().
- E finalmente, nos falta somente construir o formato do JSON.
getKey()
private String getKey(Field field) {
String value = field.getAnnotation(JsonElemento.class).key();
return value.isEmpty() ? field.getName() : value;
}
Aquele getKey() usado acima recebe um field; desse field, extraímos a chave da anotação (aquela key, lembram?).
Lembrando que se não definirmos em nossa anotação o nome da key, o nome do atributo será usado; se definirmos, será o nome que escolhemos. No atributo idade por exemplo, preferi colocar a key como pessoaIdade.
Testando nosso conversor
Agora é a hora de ver o resultado: basta criar um objeto da nossa classe de conversão e usá-lo.
public class TestaConversor {
public static void main(String[] args) {
ConversorObjetoToJson conversorObjetoToJson = new ConversorObjetoToJson();
Pessoa pessoa = new Pessoa( "Fulano", "De Ciclano", "23");
String json = conversorObjetoToJson.convertToJson(pessoa);
System.out.println(json);
}
}
O retorno do meu teste foi:
{
"nome":"Fulano",
"pessoaIdade":"23",
"sobrenome":"De Ciclano"
}
Process finished with exit code 0
É possível exportar o conversor e as anotações como .jar e usá-los em outro projeto. É assim que os frameworks que você usa em Java trabalham quando possuem anotações, com a óbvia diferença da presença de classes muito mais complexas do que o nosso conversor.
Conclusão
Por hoje é isso, pessoal. Espero que esse artigo tenha sido útil.
Anotações podem ser grandes amigas de um desenvolvedor Java, se usadas de maneira correta. Fica a sugestão para estudar mais sobre a API de Reflection do Java, uma ferramenta muito poderosa para ser usada em conjunto!
Links:
https://docs.oracle.com/javase/tutorial/java/annotations/index.html
https://docs.oracle.com/javase/tutorial/reflect/
Até a próxima!