Solucionado (ver solução)
Solucionado
(ver solução)
4
respostas

[Dúvida] Definir Construtor sem precisar definir JsonProperty

Prezados Colegas, boa tarde

Nos meus estudos, para que a desserialização com Jackson funcionasse foi necessário ou que a classe tivesse o construtor padrão, ou que o construtor com parâmetros tivesse notações JsonProperty (ou "property-based Creator"), do contrário recebia a seguinte exception:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `nome da classe` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

Surgiu a seguinte dúvida pensando no desacoplamento e na independência de tecnologia: haveria algum meio de definir uma classe apenas com o construtor com parâmetros, sem precisar das notações do Jackson? Isso num cenário em que não se possa usar o Construtor Padrão. Minha pergunta é porque se, por motivos diversos, for desejável mudar de Jackson para Gson, ou até uma outra de mesma finalidade, não seria preciso alterar todas as classes do projeto, mas sim na classe/servico "ConverteJson".

Obrigado.

4 respostas

Olá! Espero que você esteja bem.

Uma abordagem que você pode considerar é o uso de um padrão de projeto chamado "Adapter" ou "Wrapper".

A ideia é criar uma camada intermediária que cuide da conversão de JSON para objetos Java e vice-versa. Assim, você pode centralizar a lógica de conversão em uma única classe ou conjunto de classes, que chamaremos de "ConversorJson", por exemplo.

Aqui está um exemplo simplificado de como você poderia estruturar isso:

  1. Defina uma Interface para o Conversor JSON:

    public interface ConversorJson {
        <T> T fromJson(String json, Class<T> clazz);
        String toJson(Object object);
    }
    
  2. Implemente a Interface para Jackson:

    public class JacksonConversorJson implements ConversorJson {
        private final ObjectMapper objectMapper = new ObjectMapper();
    
        @Override
        public <T> T fromJson(String json, Class<T> clazz) {
            try {
                return objectMapper.readValue(json, clazz);
            } catch (IOException e) {
                throw new RuntimeException("Erro ao desserializar JSON", e);
            }
        }
    
        @Override
        public String toJson(Object object) {
            try {
                return objectMapper.writeValueAsString(object);
            } catch (JsonProcessingException e) {
                throw new RuntimeException("Erro ao serializar objeto para JSON", e);
            }
        }
    }
    
  3. Use o Conversor em seu Código: Ao invés de usar diretamente o Jackson em suas classes, utilize a interface ConversorJson. Assim, se você decidir mudar para o Gson no futuro, basta criar uma nova implementação da interface para o Gson e substituir a instância que você está usando.

Dessa forma, suas classes de domínio não precisam conhecer detalhes sobre como a serialização/deserialização é feita, mantendo o código mais limpo e desacoplado.

Espero que esta abordagem ajude a resolver sua dúvida e facilite futuras manutenções no seu projeto.

Caso este post tenha lhe ajudado, por favor, marcar como solucionado ✓.

Olá, Armano.

Obrigado pelos seus ensinamentos.

Ainda tenho uma dúvida quanto às classes de domínio: ilustrando com o seguinte exemplo, cuja classe não possa ter o construtor padrão, elas ainda precisarão receber as notações JsonProperty e/ou JsonAlias, certo?

public class Cliente {
    private int codigoCliente;
    private String nomeCliente;
    private String enderecoCliente;

    // classe não pode ter construtor padrão por definição de negócio

    @JsonIgnoreProperties(ignoreUnknown = true)
    public Cliente(@JsonProperty("rg") int codigoCliente, 
                   @JsonProperty("nome") String nomeCliente,
                   @JsonProperty("endereco") String enderecoCliente) {
        this.codigoCliente = codigoCliente;
        thos.nomeCliente = nomeCliente;
        this.enderecoCliente = enderecoCliente;
    }

// Getters e Setters
}

Se tento executar o seguinte trecho no "Main" sem as anotações JsonProperty na classe Cliente, recebo o erro abaixo:

    conteudoJson = "{"rg":1234,"nome":"John Connor","endereco":"Rua Ignorada, T800"};
    JacksonConversorJson conversor = new JacksonConversorJson();
    Cliente cliente = conversor.fromJson(conteudoJson);

Exception: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of nome da classe (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

Quando coloco as anotações, funciona.

Neste caso, caso resolvesse trocar de Jackson para Gson, precisaria trocar as anotações também nas classes de domínio, ou teria algum meio que dispensasse essa necessidade?

Mais uma vez, obrigado.

solução!

Bom dia!

Sobre sua última dúvida, você está correto: o Jackson exige as anotações @JsonProperty quando não há construtor padrão disponível, pois é assim que ele mapeia os atributos do JSON para os parâmetros do construtor. Isso, de fato, gera um forte acoplamento da sua classe de domínio com a biblioteca Jackson.

Para resolver isso sem depender de anotações como @JsonProperty e @JsonAlias, você pode optar por uma abordagem baseada em DTOs (Data Transfer Objects) ou registros intermediários. Suas entidades de domínio permanecem limpas, sem nenhuma dependência de frameworks de serialização, e a conversão fica concentrada no seu serviço de conversão.

Veja como fazer isso:

  1. Crie uma classe DTO exclusiva para o processo de serialização:

    
     public class ClienteDto {
         public int rg;
         public String nome;
         public String endereco;
    
         // Construtor padrão necessário para o Jackson
         public ClienteDto() {
         }
     }
     
  2. Sua classe de domínio permanece limpa, sem Jackson:

    
     public class Cliente {
         private final int codigoCliente;
         private final String nomeCliente;
         private final String enderecoCliente;
    
         public Cliente(int codigoCliente, String nomeCliente, String enderecoCliente) {
             this.codigoCliente = codigoCliente;
             this.nomeCliente = nomeCliente;
             this.enderecoCliente = enderecoCliente;
         }
    
         // Getters
     }
     
  3. No seu serviço de conversão, faça o mapeamento manual ou usando bibliotecas como MapStruct ou ModelMapper:

    
     String conteudoJson = "{"rg":1234,"nome":"John Connor","endereco":"Rua Ignorada, T800"}";
    
     JacksonConversorJson conversor = new JacksonConversorJson();
     ClienteDto dto = conversor.fromJson(conteudoJson, ClienteDto.class);
    
     Cliente cliente = new Cliente(dto.rg, dto.nome, dto.endereco);
     

Com isso, sua classe de domínio não depende mais do Jackson. Se no futuro quiser trocar para Gson, Moshi ou outra biblioteca, você só ajusta a implementação do ConversorJson e, se necessário, a classe DTO, mantendo sua regra de negócio intacta.

Outra possibilidade, se você estiver utilizando Java 16 ou superior, é usar Records como DTOs, o que reduz ainda mais o código boilerplate.

Fico à disposição.

Olá, Armano

Muito obrigado por mais estas lições.

Consegui fazer dos dois métodos para fins de estudos, mas optei pelo Records, o que deixou muito mais prático.