<h2>Introdução ao Pacote encoding/json em Go</h2>
<p>O pacote <code>encoding/json</code> é uma das ferramentas mais fundamentais em Go quando o assunto é trabalhar com dados estruturados. JSON (JavaScript Object Notation) tornou-se o padrão de facto para troca de dados em APIs REST, e Go oferece suporte nativo e extremamente eficiente para serialização e desserialização. A principal função deste pacote é converter estruturas Go em JSON (marshal) e vice-versa (unmarshal), permitindo que dados sejam transmitidos pela rede ou persistidos em arquivos de forma legível e interoperável.</p>
<p>O que torna o <code>encoding/json</code> especialmente poderoso em Go é sua integração com reflection e tags de estrutura. Isso significa que você não precisa escrever código boilerplate como em outras linguagens — simplesmente defina suas estruturas de forma adequada e o Go cuida do resto. Nesta aula, exploraremos não apenas o básico, mas também os padrões avançados que diferenciam um desenvolvedor profissional de alguém que apenas copia e cola código.</p>
<h2>Marshal: Convertendo Estruturas Go para JSON</h2>
<h3>Conceito Fundamental</h3>
<p>Marshal é o processo de transformar um objeto Go em sua representação JSON. Quando você chama <code>json.Marshal()</code>, o Go inspecciona a estrutura usando reflection, lê seus campos públicos (aqueles que começam com letra maiúscula) e os serializa. O resultado é um slice de bytes contendo uma representação JSON compacta. Para uma versão formatada e legível, existe <code>json.MarshalIndent()</code>.</p>
<p>Vamos começar com um exemplo prático:</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
"log"
)
type Pessoa struct {
Nome string
Idade int
Email string
Ativo bool
}
func main() {
p := Pessoa{
Nome: "Alice Silva",
Idade: 28,
Email: "alice@example.com",
Ativo: true,
}
// Marshal compacto
jsonBytes, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
fmt.Println("Compacto:", string(jsonBytes))
// Marshal com indentação
jsonFormatado, err := json.MarshalIndent(p, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println("\nFormatado:")
fmt.Println(string(jsonFormatado))
}</code></pre>
<p>Quando você executa este código, o resultado será:</p>
<pre><code>Compacto: {"Nome":"Alice Silva","Idade":28,"Email":"alice@example.com","Ativo":true}
Formatado:
{
"Nome": "Alice Silva",
"Idade": 28,
"Email": "alice@example.com",
"Ativo": true
}</code></pre>
<p>Note que os nomes dos campos na saída JSON começam com letra maiúscula, refletindo os nomes das variáveis Go. Isso é considerado uma má prática em APIs profissionais, pois JSON tipicamente usa camelCase ou snake_case. É aqui que as tags entram em cena, como veremos na próxima seção.</p>
<h3>Tratamento de Erros em Marshal</h3>
<p>Marshal pode falhar em situações específicas. O caso mais comum é tentar serializar valores que não são JSON-serializáveis, como canais ou funções. Go também rejeita valores circulares automaticamente para evitar loops infinitos. Sempre verifique o erro retornado:</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
"log"
"math"
)
type Config struct {
Timeout int
MaxValue float64
}
func main() {
// Caso 1: Valor inválido (infinito)
config := Config{
Timeout: 30,
MaxValue: math.Inf(1),
}
_, err := json.Marshal(config)
if err != nil {
fmt.Println("Erro:", err)
// Output: Erro: json: unsupported value: +Inf
}
// Caso 2: Marshal bem-sucedido com valores válidos
config2 := Config{
Timeout: 30,
MaxValue: 99.99,
}
result, _ := json.Marshal(config2)
fmt.Println(string(result))
// Output: {"Timeout":30,"MaxValue":99.99}
}</code></pre>
<h2>Tags de Estrutura: Controlando a Serialização</h2>
<h3>O Sistema de Tags em Go</h3>
<p>Tags são metadados anexados aos campos de uma estrutura que instruem o Go como processar cada campo. Em <code>encoding/json</code>, a tag padrão é <code>json:"nome_no_json"</code>. Essa sintaxe simples é incrivelmente poderosa quando combinada com opcões. As tags não afetam o comportamento em tempo de execução do Go — elas existem exclusivamente para reflexão.</p>
<p>Vamos explorar os diferentes tipos de tags e suas aplicações:</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
)
type Usuario struct {
// Nome usual: campo será serializado como "id"
ID int json:"id"
// Snake case: padrão comum em APIs REST
NomeCompleto string json:"nome_completo"
// Omitempty: não inclui o campo se estiver vazio
Biografia string json:"biografia,omitempty"
// Dash: ignora completamente o campo
SenhaHash string json:"-"
// Campo privado: ignorado automaticamente
tokenInterno string
// String tag: força conversão para string no JSON
Score int json:"score,string"
// Múltiplas opções
Ativo bool json:"ativo,omitempty"
}
func main() {
u := Usuario{
ID: 1,
NomeCompleto: "João da Silva",
Biografia: "",
SenhaHash: "super_secret_hash",
Score: 9500,
Ativo: true,
}
jsonBytes, _ := json.MarshalIndent(u, "", " ")
fmt.Println(string(jsonBytes))
}</code></pre>
<p>O output será:</p>
<pre><code class="language-json">{
"id": 1,
"nome_completo": "João da Silva",
"score": "9500",
"ativo": true
}</code></pre>
<p>Observe que <code>Biografia</code> desapareceu porque estava vazia e tinha <code>omitempty</code>, <code>SenhaHash</code> foi completamente ignorada, <code>Score</code> virou string como especificado, e o campo privado <code>tokenInterno</code> foi ignorado automaticamente.</p>
<h3>Tags Avançadas e Casos de Uso</h3>
<p>As tags podem ser combinadas para criar comportamentos sofisticados. O opção <code>string</code> é particularmente útil quando você precisa que números sejam representados como strings no JSON, um requisito comum em APIs financeiras onde a precisão é crítica.</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
)
type Produto struct {
ID int64 json:"id,string"
Nome string json:"nome"
Preco float64 json:"preco,string"
// Você pode usar string com qualquer tipo numérico
Estoque int json:"estoque,string"
// Opções podem ser combinadas
Descricao string json:"descricao,omitempty"
// Ignore este campo completamente
CustoInterno float64 json:"-"
}
func main() {
p := Produto{
ID: 987654321,
Nome: "Notebook",
Preco: 4500.99,
Estoque: 15,
Descricao: "",
CustoInterno: 2000.00,
}
json, _ := json.MarshalIndent(p, "", " ")
fmt.Println(string(json))
}</code></pre>
<p>Output:</p>
<pre><code class="language-json">{
"id": "987654321",
"nome": "Notebook",
"preco": "4500.99",
"estoque": "15"
}</code></pre>
<h2>Unmarshal: Desserializando JSON para Estruturas Go</h2>
<h3>Fundamentos da Desserialização</h3>
<p>Unmarshal é o inverso de marshal — você fornece um slice de bytes contendo JSON e uma estrutura Go como destino, e o Go preencherá essa estrutura com os dados. O tipo de dado deve ser um ponteiro, pois unmarshal precisa modificar a estrutura. Isso é absolutamente fundamental: se você esquecer do ponteiro, seu código compila mas a estrutura permanece vazia.</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
"log"
)
type Livro struct {
Titulo string json:"titulo"
Autor string json:"autor"
Ano int json:"ano"
Paginas int json:"paginas,omitempty"
}
func main() {
jsonData := []byte(`{
"titulo": "Clean Code",
"autor": "Robert Martin",
"ano": 2008,
"paginas": 464
}`)
var livro Livro
err := json.Unmarshal(jsonData, &livro)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Livro: %+v\n", livro)
// Output: Livro: {Titulo:Clean Code Autor:Robert Martin Ano:2008 Paginas:464}
}</code></pre>
<p>Um ponto importante: Go é tolerante com campos extras no JSON. Se o JSON contiver campos que não existem na estrutura Go, eles são simplesmente ignorados. Isso é uma decisão de design que facilita trabalhar com APIs que evoluem e adicionam novos campos.</p>
<h3>Tratamento de Erros em Unmarshal</h3>
<p>O unmarshal pode falhar de várias formas: JSON inválido, tipos incompatíveis, ou estrutura inesperada. A forma mais comum é quando os dados no JSON não correspondem ao tipo esperado na estrutura Go.</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
"log"
)
type Configuracao struct {
Timeout int json:"timeout"
Host string json:"host"
Porta int json:"porta"
}
func main() {
// Caso 1: JSON inválido
jsonInvalido := []byte({"timeout": 30, "host": "localhost")
var config Configuracao
err := json.Unmarshal(jsonInvalido, &config)
fmt.Println("Erro JSON inválido:", err)
// unexpected end of JSON input
// Caso 2: Tipo incompatível
jsonTipoErrado := []byte({"timeout": "trinta", "host": "localhost", "porta": 8080})
err = json.Unmarshal(jsonTipoErrado, &config)
fmt.Println("Erro tipo incompatível:", err)
// json: cannot unmarshal string into Go struct field Configuracao.timeout of type int
// Caso 3: Sucesso
jsonValido := []byte({"timeout": 30, "host": "localhost", "porta": 8080})
err = json.Unmarshal(jsonValido, &config)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Sucesso: %+v\n", config)
// Sucesso: {Timeout:30 Host:localhost Porta:8080}
}</code></pre>
<h3>Tipos Dinâmicos com interface{}</h3>
<p>Às vezes, você não conhece a estrutura do JSON antecipadamente. Nessas situações, use <code>interface{}</code> para capturar qualquer tipo. O resultado será um map ou slice que você pode inspecionar em tempo de execução.</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
)
func main() {
jsonData := []byte(`{
"nome": "Maria",
"idade": 25,
"ativo": true,
"hobbies": ["leitura", "programação"]
}`)
var dados interface{}
json.Unmarshal(jsonData, &dados)
// dados agora é um map[string]interface{}
m := dados.(map[string]interface{})
fmt.Println("Nome:", m["nome"])
fmt.Println("Idade:", m["idade"])
fmt.Println("Ativo:", m["ativo"])
hobbies := m["hobbies"].([]interface{})
for i, hobby := range hobbies {
fmt.Printf("Hobby %d: %v\n", i+1, hobby)
}
}</code></pre>
<h2>Casos Especiais e Padrões Avançados</h2>
<h3>Valores Nulos e Ponteiros</h3>
<p>Em JSON, <code>null</code> é um valor completamente diferente de um campo ausente. Para representar isso em Go, use ponteiros. Um campo de ponteiro que é <code>nil</code> será serializado como <code>null</code>, e ao desserializar, um <code>null</code> no JSON resultará em <code>nil</code> no Go.</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
)
type Perfil struct {
Nome string json:"nome"
Sobrenome *string json:"sobrenome"
Telefone *string json:"telefone,omitempty"
Idade *int json:"idade"
}
func main() {
// Serialização: null values
idade := 30
p1 := Perfil{
Nome: "Carlos",
Sobrenome: nil,
Telefone: nil,
Idade: &idade,
}
json1, _ := json.MarshalIndent(p1, "", " ")
fmt.Println("Com nil:")
fmt.Println(string(json1))
// Desserialização: null values
jsonData := []byte(`{
"nome": "Ana",
"sobrenome": null,
"idade": 28
}`)
var p2 Perfil
json.Unmarshal(jsonData, &p2)
fmt.Printf("\nDesserializado: %+v\n", p2)
fmt.Println("Sobrenome é nil?", p2.Sobrenome == nil)
}</code></pre>
<p>Output:</p>
<pre><code>Com nil:
{
"nome": "Carlos",
"sobrenome": null,
"idade": 30
}
Desserializado: {Nome:Ana Sobrenome:<nil> Telefone:<nil> Idade:0x...}
Sobrenome é nil? true</code></pre>
<h3>Tipos Customizados e Marshaler Interface</h3>
<p>Para tipos customizados ou quando você precisa de lógica especial de serialização, implemente as interfaces <code>json.Marshaler</code> e <code>json.Unmarshaler</code>. Isso permite controle total sobre como seu tipo é representado em JSON.</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
"time"
)
type Data struct {
Tempo time.Time
}
// Implementar json.Marshaler
func (d Data) MarshalJSON() ([]byte, error) {
return json.Marshal(d.Tempo.Format("02/01/2006"))
}
// Implementar json.Unmarshaler
func (d *Data) UnmarshalJSON(b []byte) error {
var s string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
t, err := time.Parse("02/01/2006", s)
if err != nil {
return err
}
d.Tempo = t
return nil
}
type Evento struct {
Nome string json:"nome"
Data Data json:"data"
}
func main() {
// Marshal
t, _ := time.Parse("2006-01-02", "2024-12-25")
evento := Evento{
Nome: "Natal",
Data: Data{Tempo: t},
}
jsonBytes, _ := json.MarshalIndent(evento, "", " ")
fmt.Println("Marshal:")
fmt.Println(string(jsonBytes))
// Unmarshal
jsonData := []byte({"nome":"Ano Novo","data":"01/01/2025"})
var evento2 Evento
json.Unmarshal(jsonData, &evento2)
fmt.Printf("\nUnmarshal: %+v\n", evento2)
}</code></pre>
<p>Output:</p>
<pre><code>Marshal:
{
"nome": "Natal",
"data": "25/12/2024"
}
Unmarshal: {Nome:Ano Novo Data:{Tempo:2025-01-01 00:00:00 +0000 UTC}}</code></pre>
<h3>Slices, Maps e Estruturas Aninhadas</h3>
<p>JSON suporta arrays e objetos aninhados de forma nativa, e Go mapeia isso perfeitamente para slices e maps. Estruturas aninhadas funcionam exatamente como esperado quando você segue as convenções de tags.</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
)
type Endereco struct {
Rua string json:"rua"
Cidade string json:"cidade"
CEP string json:"cep"
}
type Contato struct {
Tipo string json:"tipo"
Valor string json:"valor"
}
type Cliente struct {
ID int json:"id"
Nome string json:"nome"
Endereco Endereco json:"endereco"
Contatos []Contato json:"contatos"
Metadata map[string]string json:"metadata,omitempty"
}
func main() {
cliente := Cliente{
ID: 123,
Nome: "Empresa XYZ",
Endereco: Endereco{
Rua: "Av. Paulista, 1000",
Cidade: "São Paulo",
CEP: "01311-100",
},
Contatos: []Contato{
{Tipo: "email", Valor: "contato@xyz.com"},
{Tipo: "telefone", Valor: "+55 11 3000-0000"},
},
Metadata: map[string]string{
"origem": "formulário web",
"prioridade": "alta",
},
}
json, _ := json.MarshalIndent(cliente, "", " ")
fmt.Println(string(json))
}</code></pre>
<p>Output:</p>
<pre><code class="language-json">{
"id": 123,
"nome": "Empresa XYZ",
"endereco": {
"rua": "Av. Paulista, 1000",
"cidade": "São Paulo",
"cep": "01311-100"
},
"contatos": [
{
"tipo": "email",
"valor": "contato@xyz.com"
},
{
"tipo": "telefone",
"valor": "+55 11 3000-0000"
}
],
"metadata": {
"origem": "formulário web",
"prioridade": "alta"
}
}</code></pre>
<h2>Conclusão</h2>
<p>Os três pilares do domínio do <code>encoding/json</code> em Go são: (1) <strong>Marshal e Unmarshal</strong> são operações simétricas que você executará constantemente — domine o fluxo de conversão e o tratamento de erros; (2) <strong>Tags de estrutura</strong> são o mecanismo que transforma Go em uma máquina de serialização profissional — use <code>omitempty</code>, <code>string</code>, e nomes customizados de forma estratégica; (3) <strong>Casos especiais</strong> como ponteiros para null, tipos customizados com Marshaler interface, e estruturas aninhadas existem para resolver problemas reais — não são apenas recursos esotéricos, mas sim padrões que você encontrará em produção. A combinação desses conhecimentos o capacita a construir APIs robustas e interoperar com sistemas de forma confiável.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://golang.org/pkg/encoding/json/" target="_blank" rel="noopener noreferrer">Official Go Documentation - encoding/json</a></li>
<li><a href="https://golang.org/doc/effective_go#json" target="_blank" rel="noopener noreferrer">Effective Go - JSON and encoding</a></li>
<li><a href="https://golang.org/blog/json" target="_blank" rel="noopener noreferrer">The Go Blog - JSON and Go</a></li>
<li><a href="https://dave.cheney.net/practical-go" target="_blank" rel="noopener noreferrer">Dave Cheney - Practical Go: Real-world advice for writing maintainable Go programs</a></li>
<li><a href="https://www.jetbrains.com/help/go/json.html" target="_blank" rel="noopener noreferrer">GoLand IDE - Working with JSON in Go</a></li>
</ul>
<p><!-- FIM --></p>