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