JSON: encoding e decoding
1. Introdução ao JSON em Go
O pacote encoding/json da biblioteca padrão do Go é a ferramenta fundamental para trabalhar com JSON. Ele oferece funções robustas para converter estruturas de dados Go em JSON (encoding) e vice-versa (decoding), com suporte a struct, map, slice e interface{}.
As tags de struct são um recurso poderoso para controlar a serialização:
type Usuario struct {
ID int `json:"id"`
Nome string `json:"nome"`
Email string `json:"email,omitempty"`
CriadoEm time.Time `json:"criado_em"`
SenhaHash string `json:"-"`
}
2. Encoding de JSON (Marshaling)
A função json.Marshal() converte estruturas Go em bytes JSON:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Produto struct {
Nome string `json:"nome"`
Preco float64 `json:"preco"`
Estoque int `json:"estoque"`
}
func main() {
p := Produto{Nome: "Notebook", Preco: 3500.50, Estoque: 10}
// Marshal básico
dados, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(dados))
// {"nome":"Notebook","preco":3500.5,"estoque":10}
// Marshal com indentação
dadosIndent, err := json.MarshalIndent(p, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(dadosIndent))
}
Tratamento de erros importantes:
- Campos não exportados (minúsculos) são ignorados
- Tipos não suportados (canais, funções) geram erro
- Ciclos em structs causam erro
3. Decoding de JSON (Unmarshaling)
json.Unmarshal() converte bytes JSON em estruturas Go:
type Pedido struct {
ID int `json:"id"`
Cliente string `json:"cliente"`
Total float64 `json:"total"`
}
func main() {
jsonData := `{"id": 1, "cliente": "Maria", "total": 250.75}`
var pedido Pedido
err := json.Unmarshal([]byte(jsonData), &pedido)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", pedido) // {ID:1 Cliente:Maria Total:250.75}
}
Decoding para JSON dinâmico:
func decodingDinamico() {
jsonData := `{"nome": "João", "idade": 30, "ativo": true, "endereco": {"cidade": "SP"}}`
var dados map[string]interface{}
err := json.Unmarshal([]byte(jsonData), &dados)
if err != nil {
log.Fatal(err)
}
// Acessando valores com type assertion
nome := dados["nome"].(string)
idade := dados["idade"].(float64) // JSON numbers viram float64
fmt.Printf("Nome: %s, Idade: %.0f\n", nome, idade)
}
Erros comuns:
- JSON malformado (chaves faltando, vírgulas extras)
- Tipos incompatíveis (string vs number)
- Campos obrigatórios ausentes na struct
4. Streams de JSON: Encoder e Decoder
Para processamento contínuo, use json.NewEncoder() e json.NewDecoder():
func processarStream() {
// Escrita em stream
var buffer bytes.Buffer
encoder := json.NewEncoder(&buffer)
encoder.Encode(Produto{Nome: "Mouse", Preco: 89.90, Estoque: 50})
encoder.Encode(Produto{Nome: "Teclado", Preco: 199.90, Estoque: 30})
fmt.Println(buffer.String())
// {"nome":"Mouse","preco":89.9,"estoque":50}
// {"nome":"Teclado","preco":199.9,"estoque":30}
// Leitura de stream (útil para grandes arquivos)
leitor := strings.NewReader(`{"nome":"Monitor","preco":1500.00,"estoque":5}`)
decoder := json.NewDecoder(leitor)
var prod Produto
decoder.Decode(&prod)
fmt.Printf("%+v\n", prod)
}
Uso prático com HTTP:
func handlerHTTP(w http.ResponseWriter, r *http.Request) {
var usuario Usuario
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&usuario); err != nil {
http.Error(w, "JSON inválido", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
5. Controle Fino com Tags e Opções
type Config struct {
Nome string `json:"nome"`
Ativo bool `json:"ativo,omitempty"` // Omite se false
Preco float64 `json:"preco,string"` // Força string
Ignorado string `json:"-"` // Ignora campo
Tags []string `json:"tags,omitempty"` // Omite se nil/vazio
}
func main() {
c := Config{Nome: "Servidor", Ativo: false, Preco: 99.99, Ignorado: "secreto"}
dados, _ := json.Marshal(c)
fmt.Println(string(dados))
// {"nome":"Servidor","preco":"99.99"}
// Ativo foi omitido (false), Ignorado foi ignorado (-)
}
6. Interfaces e Customização com MarshalJSON/UnmarshalJSON
Implemente json.Marshaler e json.Unmarshaler para controle total:
type DataCustomizada struct {
time.Time
}
func (d DataCustomizada) MarshalJSON() ([]byte, error) {
return json.Marshal(d.Format("02/01/2006"))
}
func (d *DataCustomizada) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
parsed, err := time.Parse("02/01/2006", str)
if err != nil {
return err
}
d.Time = parsed
return nil
}
type Evento struct {
Nome string `json:"nome"`
Data DataCustomizada `json:"data"`
}
func main() {
evento := Evento{Nome: "Conferência", Data: DataCustomizada{time.Now()}}
dados, _ := json.Marshal(evento)
fmt.Println(string(dados))
// {"nome":"Conferência","data":"15/03/2025"}
jsonStr := `{"nome":"Workshop","data":"20/04/2025"}`
var e Evento
json.Unmarshal([]byte(jsonStr), &e)
fmt.Println(e.Data.Format("2006-01-02")) // 2025-04-20
}
Mascarar dados sensíveis:
type UsuarioSeguro struct {
Nome string `json:"nome"`
Email string `json:"email"`
Senha string `json:"-"`
}
func (u UsuarioSeguro) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Nome string `json:"nome"`
Email string `json:"email"`
Senha string `json:"senha"`
}{
Nome: u.Nome,
Email: u.Email,
Senha: "***",
})
}
7. Boas Práticas e Padrões
Validação de JSON:
func validarJSON() {
dados := `{"nome": "João", "idade": 30}`
if !json.Valid([]byte(dados)) {
log.Fatal("JSON inválido")
}
var usuario struct {
Nome string `json:"nome"`
Idade int `json:"idade"`
}
if err := json.Unmarshal([]byte(dados), &usuario); err != nil {
log.Fatal(err)
}
}
Uso de json.RawMessage para decoding adiado:
type RespostaFlexivel struct {
Status string `json:"status"`
Dados json.RawMessage `json:"dados"` // Decodifica depois
}
func processarResposta() {
jsonStr := `{"status":"ok","dados":{"id":1,"nome":"Produto A"}}`
var resp RespostaFlexivel
json.Unmarshal([]byte(jsonStr), &resp)
// Decodifica apenas quando necessário
if resp.Status == "ok" {
var produto struct {
ID int `json:"id"`
Nome string `json:"nome"`
}
json.Unmarshal(resp.Dados, &produto)
fmt.Printf("Produto: %d - %s\n", produto.ID, produto.Nome)
}
}
Performance com buffers reutilizáveis:
var bufferPool = sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
func marshalEficiente(v interface{}) ([]byte, error) {
buf := bufferPool.Get().(*bytes.Buffer)
defer bufferPool.Put(buf)
buf.Reset()
encoder := json.NewEncoder(buf)
if err := encoder.Encode(v); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
Referências
- Documentação oficial do pacote encoding/json — Referência completa com todas as funções, tipos e exemplos da biblioteca padrão
- JSON and Go - The Go Blog — Artigo oficial da equipe Go explicando conceitos fundamentais de JSON em Go
- How to Parse JSON in Golang (With Examples) — Tutorial prático com exemplos de encoding e decoding, incluindo JSON aninhado
- Go by Example: JSON — Exemplos concisos e diretos de marshaling e unmarshaling com diferentes estruturas
- Mastering JSON in Go — Artigo avançado sobre armadilhas comuns e boas práticas ao trabalhar com JSON em Go
- Efficient JSON Parsing in Go — Guia sobre performance com streams JSON e reutilização de recursos