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

[Dúvida] Não faz a conversão!

Pessoal, estou apanhando desse código:

    private void buscarLivroWeb() {
        System.out.println("Digite o nome do livro que deseja buscar:");
        var nomeLivro = leitura.nextLine();
        var json = consumoApi.obterDados(ENDERECO + nomeLivro.replace(" ", "+"));
        System.out.println(json);
        DadosLivro dados = conversor.obterDados(json, DadosLivro.class);
        System.out.println(dados);
    }

Faz a busca e apresenta o json tudo certinho mas não converte, retorna null, aqui está classe e a interface de conversão:

public interface IConverterDados {

    // Receber um Json com um livro e retornar um DadosLivro
    <T> T  obterDados(String json, Class<T> classe);
}

public class ConverterDados implements IConverterDados {
    private ObjectMapper mapper = new ObjectMapper();

    @Override
    public <T> T obterDados(String json, Class<T> classe) {
        try {
            return mapper.readValue(json, classe);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}

Aqui o resultado da execução:

Opção: 
1
Digite o nome do livro que deseja buscar:
dom casmurro
{"count":1,"next":null,"previous":null,"results":[{"id":55752,"title":"Dom Casmurro","authors":[{"name":"Machado de Assis","birth_year":1839,"death_year":1908}],"translators":[],"subjects":["Adultery -- Fiction","Authorship -- Fiction","Catholic Church -- Fiction","Reminiscing in old age -- Fiction"],"bookshelves":[],"languages":["pt"],"copyright":false,"media_type":"Text","formats":{"text/plain; charset=us-ascii":"https://www.gutenberg.org/ebooks/55752.txt.utf-8","text/html":"https://www.gutenberg.org/ebooks/55752.html.images","text/html; charset=iso-8859-1":"https://www.gutenberg.org/files/55752/55752-h/55752-h.htm","application/epub+zip":"https://www.gutenberg.org/ebooks/55752.epub3.images","application/x-mobipocket-ebook":"https://www.gutenberg.org/ebooks/55752.kf8.images","text/plain; charset=iso-8859-1":"https://www.gutenberg.org/files/55752/55752-8.txt","application/rdf+xml":"https://www.gutenberg.org/ebooks/55752.rdf","image/jpeg":"https://www.gutenberg.org/cache/epub/55752/pg55752.cover.medium.jpg","application/octet-stream":"https://www.gutenberg.org/cache/epub/55752/pg55752-h.zip"},"download_count":915}]}
DadosLivro[titulo=null, idioma=null, numeroDownloads=null]
11 respostas

Oi Everaldo, essa sua classe DadosLivro é uma classe Java comum ou é um Record? O Modelmapper não funciona com Records, acontece justamente isso

Olá, Everaldo. Uma coisa que observei é que os dados que você quer acessar estão chegando dentro da propriedade "results" em forma de Lista. Talvez esse seja o motivo do jackson não estar conseguindo mapear corretamente, se puder envie a sua classe DadosLivro para investigarmos melhor

Bom dia, Algacyr, obrigado pelo apoio.... não consegui evoluir.


@JsonIgnoreProperties(ignoreUnknown = true)
public record DadosLivro(@JsonAlias({"title"}) String titulo,
                         @JsonAlias({"authors"}) List<DadosAutor> autores,
                         @JsonAlias("languages") List<String> idiomas,
                         @JsonAlias("download_count") Integer numeroDownloads) {
}
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public record Dados(
        @JsonAlias("results") List<DadosLivro> results){

}

Estou usando a API Gutendex.

Dados vindos a API:

  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 55752,
      "title": "Dom Casmurro",
      "authors": [
        {
          "name": "Machado de Assis",
          "birth_year": 1839,
          "death_year": 1908
        }
      ],
      "translators": [],
      "subjects": [
        "Adultery -- Fiction",
        "Authorship -- Fiction",
        "Catholic Church -- Fiction",
        "Reminiscing in old age -- Fiction"
      ],
      "bookshelves": [],
      "languages": [
        "pt"
      ],
      "copyright": false,
      "media_type": "Text",
      "formats": {
        "text/plain; charset=us-ascii": "https://www.gutenberg.org/ebooks/55752.txt.utf-8",
        "text/html": "https://www.gutenberg.org/ebooks/55752.html.images",
        "text/html; charset=iso-8859-1": "https://www.gutenberg.org/files/55752/55752-h/55752-h.htm",
        "application/epub+zip": "https://www.gutenberg.org/ebooks/55752.epub3.images",
        "application/x-mobipocket-ebook": "https://www.gutenberg.org/ebooks/55752.kf8.images",
        "text/plain; charset=iso-8859-1": "https://www.gutenberg.org/files/55752/55752-8.txt",
        "application/rdf+xml": "https://www.gutenberg.org/ebooks/55752.rdf",
        "image/jpeg": "https://www.gutenberg.org/cache/epub/55752/pg55752.cover.medium.jpg",
        "application/octet-stream": "https://www.gutenberg.org/cache/epub/55752/pg55752-h.zip"
      },
      "download_count": 1111
    }
  ]
}

Olá, acho que você não viu a minha resposta...

Os seus DTOs são records, isso não vai funcionar com ModelMapper... Você precisa usar classes Java.

Exemplo de DTO com classe Java + biblioteca Lombok:

@Getter
@ToString
@RequiredArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class DadosLivro {

    @JsonAlias({"title"})
    private final String title,

    @JsonAlias({"autors"})
    private final List<DadosAutor> autores,
    
    @JsonAlias({"languages"})
    private final List<String> idiomas,
    
    @JsonAlias({"download_count"})
    private final numeroDownloads
}

@Getter
@ToString
@RequiredArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Dados {
        
        @JsonAlias("results") 
        private final List<DadosLivro> results;
}

Caso não tenha o Lombok é só gerar os getters e construtor obrigatório manualmente. Você pode construi-los como bem quiser, mas esses exemplos seguem as boas práticas de um DTO, que é ser uma classe imutável, apenas para transferência de dados. O Record já segue toda a convenção, por isso apenas criamos sem preocupação com a forma que vamos fazê-lo.

Faça isso com seus DTOs e acredito que vai resolver seu problema.

Olá, Everaldo, tenta o seguinte, substitui DadosLivro por Dados:

DadosLivro dados = conversor.obterDados(json, DadosLivro.class);
Dados dados = conversor.obterDados(json, Dados.class);

Pelo que analisei no seu código, os dados que chegam da API são correspondentes à sua record Dados, que contém a lista de DadosLivro.

O código atual está tentando desserializar o json em DadosLivro.

Olá Mateus, eu vi a sua resposta, mas confesso que não entendi, sou novo no Java, o que seria DTO e ModelMapper?

Boa tarde Mateus, obrigado pela dica, tentei mas não deu certo.... não sei onde está o problema.

Bem observado pelo Algacyr! Não funcionou fazendo o ajuste dos DTO e do Algacyr?

Boa noite, obrigado Mateus e Algacyr, agora sim funcionou troquei o DadosLivro pelo Dados, aí deu certo, a ajuda de vocês foi fundamental, mais uma vez obrigado pelo interesse em me ajudar a resolver isso.

Por nada, Everaldo! O problema em si com certeza era o que o Algacyr comentou... Eu pensei ser o ModelMapper porque já tive problemas com ele que resultava no mesmo comportamento.

Perdão Algacyr, eu não havia visto que você me mencionou...

O DTO é um padrão de design cujo objetivo é a transferência de dados entre diferentes camadas da aplicação. Esse conceito não é exclusivo do Java, qualquer linguagem pode adotar essa técnica, e no caso do Java (a partir da versão 14), nós temos um recurso da linguagem que facilita a criação de um DTO utilizando os chamados Records, que nada mais são do que classes imutáveis, com um construtor completo e obrigatório, sem métodos setters. Eles são perfeitos para implementar esse padrão de design DTO.

Digamos que você tem uma entidade complexa:

public class User {

    private String cpf;
    private String username;
    private String password;
    private String name;
}

Muitos desses dados são sensíveis demais para você retornar a entidade em respostas, para isso você pode criar um DTO e usa-lo nas suas respostas sem comprometer os outros dados da entidade:

public class UserDTO {

    private final String name;
    
    public UserDTO(String name) {
        this.name = name;
    }
}

O ModelMapper é uma biblioteca que facilita o mapeamento de objetos, nos dando mais flexibilidade para converter um objeto em outro, principalmente em contextos onde objetos de diferentes camadas da aplicação (como DTOs e entidades) precisam ser convertidos entre si.

Porém, a biblioteca ModelMapper não funciona bem com os Records, por isso é melhor usar classes Java padrão quando for usa-la. Se não estou enganado, você conseguer converter uma classe Java para uma classe Record, mas não consegue converter um Record para uma classe Java, o retorno será a classe com todos os atributos null. Inclusive eu não me lembrei desse detalhe, por isso imaginei que o problema fosse porque ele estava usando Record + ModelMapper

solução!

Olá, Mateus. Muito obrigado pela explicação, foi a primeira vez que ouvi falar em DTO, talvez esse tópico seja tratado posteriormente no curso, estou caminhando para a parte sobre persistência de dados. De qualquer forma, como você já me apresentou o tema, vou pesquisar um pouco mais sobre.

E que bom, Everaldo, que o problema tenha sido resolvido, fico feliz em ter contribuido. Acho que foi a primeira vez que consegui ajudar alguém com Java! Você pode marcar o tópico como solucionado, pra sinalizar aos demais da comunidade que a soluç