Enviando JSON em uma requisição POST em Go: de "como isso funciona" a "feito e esquecido"
Você já ficou preso no Stack Overflow, tentando entender por que sua requisição POST com JSON em Go não é aceita pelo servidor? Familiar: parece que está tudo certo, mas a resposta é 400 Bad Request ou corpo vazio. Acontece que em Go existem várias maneiras de enviar JSON, e escolher a correta não é apenas uma questão de gosto, mas uma questão de estabilidade do seu microsserviço.
Neste artigo, não vamos apenas mostrar código. Vamos analisar três abordagens: desde a ingênua (que funciona, mas é perigosa) até a solução de produção com tratamento adequado de erros e contextos. Se você programa em Go para APIs, bots ou integrações, esta é uma leitura obrigatória.
Por que este tópico é tão popular?
Go é uma linguagem para serviços de rede. Mais cedo ou mais tarde, qualquer desenvolvedor enfrenta a tarefa: "enviar JSON no corpo de uma requisição POST". Parece simples, mas há nuances:
- É necessário serializar corretamente a estrutura em JSON (marshalling).
- É importante não esquecer o cabeçalho
Content-Type: application/json. - É preciso fechar corretamente o corpo da resposta.
- Deseja-se evitar vazamentos de memória e pânicos.
Vamos analisar cada abordagem com exemplos práticos.
Abordagem 1: "Na gambiarra" — bytes.NewBuffer + json.Marshal
Esta é a maneira mais simples. Pegamos uma estrutura, fazemos o marshalling em bytes, colocamos em um buffer e enviamos. Muitos desenvolvedores Go iniciantes escrevem exatamente assim.
package main
import ( "bytes" "encoding/json" "fmt" "net/http")
type User struct { Name string `json:"name"` Email string `json:"email"`}
func main() { user := User{Name: "João Silva", Email: "joao@example.com"} // Passo 1: fazer marshalling da estrutura para JSON jsonData, err := json.Marshal(user) if err != nil { fmt.Println("Erro de marshalling:", err) return } // Passo 2: criar requisição POST resp, err := http.Post( "https://api.example.com/users", "application/json", bytes.NewBuffer(jsonData), ) if err != nil { fmt.Println("Erro na requisição:", err) return } defer resp.Body.Close() fmt.Println("Status:", resp.Status)}Prós e contras desta abordagem
- Pró: Mínimo de código, claro para iniciantes.
- Contra: Sem flexibilidade. Você não pode adicionar cabeçalhos personalizados (autenticação, trace-id). Sem controle sobre timeouts. Se o servidor retornar um erro, você não o tratará.
- Contra: Problema potencial: se
json.Marshalretornar um erro, você o capturará, mas no código real muitas vezes esquecem de verificar.
Esta abordagem serve para protótipos e testes. Para produção, não.
Abordagem 2: "A correta" — http.NewRequest + json.NewEncoder
A comunidade Go recomenda usar http.NewRequest e json.NewEncoder. Por quê? Porque oferece controle total sobre a requisição e é mais eficiente em termos de memória.
package main
import ( "bytes" "encoding/json" "fmt" "net/http" "time")
type Order struct { ID int `json:"id"` Amount float64 `json:"amount"` Items []string `json:"items"`}
func sendOrder(order Order) error { // Criar buffer e encoder var buf bytes.Buffer enc := json.NewEncoder(&buf) // Codificar diretamente no buffer (sem []byte intermediário) if err := enc.Encode(order); err != nil { return fmt.Errorf("erro na codificação JSON: %w", err) } // Criar requisição req, err := http.NewRequest("POST", "https://api.example.com/orders", &buf) if err != nil { return fmt.Errorf("erro ao criar requisição: %w", err) } // Definir cabeçalhos req.Header.Set("Content-Type", "application/json") req.Hea