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:

  1. 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.
  2. Depois, criamos um HashMap, com chave e valor do tipo String onde estarão as informações do JSON.
  3. 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()).
  4. Precisamos usar esse setAccessible, pois declaramos os campos como private.
  5. 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.
  6. Depois, já com todas as informações no HashMap, vamos transformá-lo em String utilizando o .entrySet().
  7. 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!