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

Envio de Json e Imagem em uma requisição POST com Spring Boot

Olá,

Gostaria de saber como faço para enviar uma imagem e um json em uma requisição post, usando o Spring Boot.

Criei um projeto, onde disponibilizo um recurso de transferir um arquivo para o servidor. Isso funciona bem.

Porém, desejo agora enviar dados cadastrais de um pessoa e também uma foto da mesma, mas não consegui, gostaria de saber como posso resolver essa questão ?

Segue o repositório do projeto. https://github.com/rrodhrigues/contact-api.git

5 respostas
solução!

Fala Renato, tudo bem ?

Imagino que você vai enviar a requisição para uma app Spring Boot, seja de um navegador, ou de uma app qualquer. Se for isso imagino que teria duas opções. A primeira delas é fazer uma requisição com Content-type multipart (ao invés de application/json) e enviar o json como um simples conteúdo textual de um campo.

Exemplo:

Controller esperando receber um conteudo String (json) e um MultipartFile, pra poder contar com as facilidades do Spring MVC pra esse arquivo, por exemplo, salvar localmente com foto.transferTo(new File(...)), etc, etc.

@RestController
public class TesteController {

    @RequestMapping(method=RequestMethod.POST, value="/send")
    public ResponseEntity<String> receiveData(String pessoaJson, MultipartFile foto) {

        ObjectMapper mapper = new ObjectMapper();
        PessoaDto pessoa = null;

        try {
            pessoa = mapper.readValue(pessoaJson, PessoaDto.class);
        } catch (IOException e) {
            return ResponseEntity.badRequest().body("Não foi possível ler o json");
        }

        System.out.println(pessoa);
        System.out.println(foto.getOriginalFilename());
        return ResponseEntity.ok("Deu certo!");
    }
}

Pagina qualquer com script montando a requisição do tipo multipart e colocando a foto selecionada, mais o conteúdo json a partir de dados preenchidos por um usuário.

<!DOCTYPE html>
...
<script type="text/javascript">

    function sendData() {        
        var pessoa = { 
            nome: document.querySelector('#nome').value, 
            idade: document.querySelector('#idade').value 
        };
        var foto = document.querySelector('#imagePicker').files[0];

        var formData = new FormData();
        formData.append("pessoaJson", JSON.stringify(pessoa));
        formData.append("foto", foto);

        fetch('http://localhost:8080/send', {
            method: 'POST',
            body: formData
        })
        .then(response => {
            if(!response.ok)
                throw new Error("não foi possível completar cadastro");

            return response.text();
        })
        .then(data => alert(data));
    }

</script>
...
<body>
        <label for="nome">Nome:</label>
        <input type="text" id="nome"/>

        <label for="idade">Idade:</label>
        <input type="text" id="idade"/>

        <label for="imagePicker">Foto:</label>
        <input type="file" id="imagePicker" />

        <input type="button" onclick="sendData()" value="Enviar" />
</body>
...

O inconveniente dessa abordagem é que você não vai conseguir fazer o Spring montar a partir do json o objeto que representa Pessoa, dado que o content-type da request é diferente. Você vai precisar escrever o código que pega a informação que veio no campo e converter usando alguma lib json (no exemplo a Jackson Json) para o seu objeto. Você pode fazer isso tanto no controller, quanto criar um converter String <-> PessoaDto pra facilitar o código do controller.

Uma questão que gostaria de abrir é: Será que precisa mesmo que seja um json que venha junto com a foto ? Dado que, pra aproveitar o upload precisamos montar um request multipart (através do FormData), porque não montar esse objeto normalmente, campo a campo, como num simples formulário ?

Dessa forma ficaria assim:

<!-- conteudo omitido -->
<script type="text/javascript">

    function sendData() {        

        var formData = new FormData();
        formData.append("nome", document.querySelector('#nome').value);
        formData.append("idade", document.querySelector('#idade').value);
        formData.append("foto", document.querySelector('#imagePicker').files[0]);

        fetch('http://localhost:8080/send', {
            method: 'POST',
            body: formData
        })
        .then(response => {
            if(!response.ok)
                throw new Error("não foi possível completar cadastro");

            return response.text();
        })
        .then(data => alert(data));
    }

</script>

Controller recebendo direto o conteúdo do formData, sendo possível já fazer o binding pro seu objeto

    @RequestMapping(method=RequestMethod.POST, value="/send")
    public ResponseEntity<String> receiveData(PessoaDto pessoaDto, MultipartFile foto) {

        System.out.println(pessoaDto);
        System.out.println(foto.getOriginalFilename());

        // salva -> foto.transferTo(new File("caminhoDoArquivo"))
        // ou manda para um serviço de storage -> String urlFoto = s3.save(foto.getInputStream())
        // monta seu objeto de domínio -> new Pessoa(nome, idade, urlFoto)
        // segue a vida -> salva num banco .. etc, etc


        return ResponseEntity.ok("Deu certo!");
    }

Olha como tudo fica muito mais fácil. Acho que isso pode ajudar.

A dificuldade desse caso é que a request (POST) deve ser feita ou com Content-type multipart/form-data (que privilegia o upload, e posterior manipulação do arquivo no servidor), ou application/json (que privilegia a conversão de um conteúdo desse formato para seus objetos Java). Não tem como fazer isso parcialmente.

Ah! A segunda opção que tinha comentado antes era justamente o oposto da primeira. Fazer a requisição com conteúdo application/json e enviar apenas a representação serializada da imagem.

{
    "nome": "Rafael",
    "idade": 25,
    "foto": "ksjbkjsbdkjdfbjvdfjvbdjkfdfjnvxfvlsfnvjdfnvjdfv" // qualquer conteúdo de imagem serializado, ou em base64 por exemplo
}

Aí você poderia dizer que o controller espera receber JSON.

@RequestMapping(method=RequestMethod.POST, value="/send", consumes=MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<String> receiveData(@RequestBody PessoaDto pessoaDto) {

        System.out.println(pessoaDto);

        // ...
        return ResponseEntity.ok("Deu certo!");
    }

O Spring já vai usar a Jackson para converter direto o json para o seu objeto. O problema é que você não tem mais o suporte que tinha antes do MultipartFile, e vai ter que usar também algum mecanismo pra "remontar" a imagem a partir do conteúdo textual vindo no Json.

É mais uma questão de escolha nesse momento mesmo.

Espero ter ajudado. Abraço!

Olá Rafael,

Muito obrigado pelas soluções,

A segunda me interessei bastante, porém, procurando na internet, não descobri como fazer uma serialização da imagem.

Tem algum tutorial para que eu possa ler ?

Desde já grato

Fala Renato, tudo bem ?

Foi mal a demora. Segue:

  • Links sobre Converter imagem em string base64 (encode)

https://bytenota.com/javascript-convert-image-to-base64-string/

https://gist.github.com/gfcarvalho/9502621

  • Links sobre Converter string base64 em imagem (decode)

https://javapointers.com/tutorial/java-convert-image-to-base64-string-and-base64-to-image/

Espero ter ajudado. Abraço!

Ajudou sim, muito obrigado !!!