Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

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ç