A mágica do JSON em Go: por que alguns campos são visíveis e outros não?
\nVocê já se deparou com uma situação em que sua struct Go envia na resposta JSON algo que o cliente não precisa ver? Por exemplo, a senha de um usuário, o ID interno do banco de dados ou uma chave secreta. Ou, ao contrário, precisa mostrar um campo apenas sob uma condição específica (por exemplo, se não estiver vazio).
\nEsse problema é um dos mais populares no Stack Overflow, e não é à toa. Trabalhar com JSON em Go é uma arte de equilíbrio entre o rigor dos tipos e a flexibilidade da representação. Hoje vamos abordar todas as formas: desde a tag simples json:"-" até soluções personalizadas complexas com interfaces. Vamos nessa!
1. A maneira mais simples: a tag json:"-"
\nGo permite controlar a serialização de uma struct por meio de tags especiais. Se você quiser ocultar permanentemente um campo do JSON, basta adicionar json:"-".
type User struct {\n Name string `json:"name"`\n Password string `json:"-"`\n Age int `json:"age"`\n}\n\nfunc main() {\n u := User{Name: "Alice", Password: "supersecret", Age: 30}\n data, _ := json.Marshal(u)\n fmt.Println(string(data))\n // Saída: {"name":"Alice","age":30}\n}\nO campo Password desapareceu, como se nunca tivesse existido. Ideal para dados confidenciais que nunca devem sair do servidor.
2. Ocultação condicional: omitempty
\nÀs vezes, é necessário ocultar um campo apenas se ele estiver vazio. Para isso, usa-se a opção omitempty (abreviação de "omit if empty").
type Product struct {\n Name string `json:"name"`\n Description string `json:"description,omitempty"`\n Price float64 `json:"price,omitempty"`\n}\n\nfunc main() {\n p1 := Product{Name: "Livro", Price: 500.0}\n data1, _ := json.Marshal(p1)\n fmt.Println(string(data1)) // {"name":"Livro","price":500}\n\n p2 := Product{Name: "Curso"}\n data2, _ := json.Marshal(p2)\n fmt.Println(string(data2)) // {"name":"Curso"}\n}\nImportante: omitempty funciona apenas com valores "zero": "", 0, false, nil (para slices, maps, ponteiros).
Armadilha com campos numéricos
\nCuidado: se Price for igual a 0, ele também será ocultado. Em algumas lógicas de negócio, isso é inaceitável (um produto gratuito também é um preço). Nesses casos, é melhor usar ponteiros.
3. Uso de ponteiros (*) e interfaces
\nPonteiros são seus melhores amigos quando você precisa distinguir entre "valor não informado" e "valor igual a zero".
\ntype UserProfile struct {\n Name string `json:"name"`\n Age *int `json:"age,omitempty"` // ponteiro para int\n Bio *string `json:"bio,omitempty"`\n}\n\nfunc main() {\n // Idade não informada — campo será ocultado\n u1 := UserProfile{Name: "João"}\n data1, _ := json.Marshal(u1)\n fmt.Println(string(data1)) // {"name":"João"}\n\n // Idade = 0 — é um valor significativo\n zero := 0\n u2 := UserProfile{Name: "Maria", Age: &zero}\n data2, _ := json.Marshal(u2)\n fmt.Println(string(data2)) // {"name":"Maria","age":0}\n}\nEssa abordagem é frequentemente usada em APIs REST, onde é necessário atualizar apenas os campos enviados (atualização parcial, requisições PATCH).
\n\n4. Serialização personalizada: interface json.Marshaler
\nSe as tags padrão não forem suficientes, você pode controlar totalmente o processo. Implemente a interface json.Marshaler e escreva sua própria lógica.
type DynamicUser struct {\n ID int\n Name string\n ShowEmail bool\n Email string\n}\n\nfunc (u DynamicUser) MarshalJSON() ([]byte, error) {\n // Criamos uma struct anônima apenas para serialização\n m := map[string]interface{}{\n "id": u.ID,\n "name": u.Name,\n }\n if u.ShowEmail {