Processamento de JSON
Lição 21: Processamento de JSON
Analogia da Vida
Imagine que você é um tradutor. JSON é a "lingua franca" mais amplamente utilizada no mundo. Quando um programa Go precisa se comunicar com outros sistemas (frontend, API, banco de dados), você precisa:
- Codificar (Marshal): "Traduzir" structs Go para formato JSON para enviar
- Decodificar (Unmarshal): "Traduzir" JSON recebido de volta para structs Go para uso
Assim como um tradutor precisa entender as regras e expressões idiomáticas de ambos os idiomas, o pacote encoding/json do Go é sua ferramenta poderosa para lidar com JSON.
Conceitos Centrais
| Conceito | Descrição |
|---|---|
| Serialização (Marshal) | Converter estruturas de dados Go para slices de bytes JSON |
| Deserialização (Unmarshal) | Analisar slices de bytes JSON em estruturas de dados Go |
| Tag de Struct | Metadados que controlam nomes de campos JSON e comportamento |
| Streaming | Usar Decoder/Encoder para grandes volumes de dados ou streams de rede |
| Serialização Personalizada | Implementar interfaces Marshaler/Unmarshaler para lógica de conversão customizada |
Sintaxe Básica e Uso
1. Importar o Pacote
import "encoding/json"
2. Serialização: Struct → JSON
// Definir struct
type User struct {
Name string
Age int
Email string
}
user := User{Name: "Alice", Age: 28, Email: "alice@example.com"}
// Serializar
data, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
// Saída: {"Name":"Alice","Age":28,"Email":"alice@example.com"}
json.Marshal retorna []byte, que precisa de conversão com string() para imprimir JSON legível.
3. Deserialização: JSON → Struct
jsonStr := `{"Name":"Bob","Age":32,"Email":"bob@example.com"}`
var user User
err := json.Unmarshal([]byte(jsonStr), &user)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Nome: %s, Idade: %d\n", user.Name, user.Age)
// Saída: Nome: Bob, Idade: 32
Unmarshal deve ser um ponteiro, caso contrário as alterações não terão efeito.
4. Tags de Struct
type Product struct {
ID int `json:"id"` // Especificar nome do campo JSON
Name string `json:"name"` // Nomes em minúsculas são mais amigáveis ao JSON
Price float64 `json:"price"`
Desc string `json:"description,omitempty"` // Omitir quando vazio
internal string `json:"-"` // Ignorar completamente este campo
}
omitempty: Quando o campo é um valor zero, ele será omitido da saída JSON-: Este campo nunca aparecerá no JSON- Nomes de tags têm prioridade sobre nomes de campos
5. Mapeamentos Comuns de Tipos
| Tipo Go | Tipo JSON |
|---|---|
string |
string |
int, float64 |
number |
bool |
boolean |
nil |
null |
[]T |
array |
map[string]T |
object |
struct |
object |
Exemplos
Exemplo: Serialização e Deserialização JSON Básica (Dificuldade ⭐)
package main
import (
"encoding/json"
"fmt"
"log"
)
// Struct Book
type Book struct {
Title string `json:"title"`
Author string `json:"author"`
Pages int `json:"pages"`
Tags []string `json:"tags"`
InStock bool `json:"in_stock"`
}
func main() {
// === Serialização ===
book := Book{
Title: "Go em Ação",
Author: "João Silva",
Pages: 350,
Tags: []string{"Programação", "Go", "Backend"},
InStock: true,
}
// Impressão formatada (com indentação)
jsonData, err := json.MarshalIndent(book, "", " ")
if err != nil {
log.Fatal("Falha na serialização:", err)
}
fmt.Println("=== Resultado da Serialização ===")
fmt.Println(string(jsonData))
// === Deserialização ===
jsonStr := `{
"title": "Dominando Go",
"author": "Maria Santos",
"pages": 480,
"tags": ["Go", "Avançado", "Concorrência"],
"in_stock": false
}`
var newBook Book
err = json.Unmarshal([]byte(jsonStr), &newBook)
if err != nil {
log.Fatal("Falha na deserialização:", err)
}
fmt.Println("\n=== Resultado da Deserialização ===")
fmt.Printf("Título: %s\n", newBook.Title)
fmt.Printf("Autor: %s\n", newBook.Author)
fmt.Printf("Tags: %v\n", newBook.Tags)
fmt.Printf("Em Estoque: %v\n", newBook.InStock)
}
Saída:
=== Resultado da Serialização ===
{
"title": "Go em Ação",
"author": "João Silva",
"pages": 350,
"tags": [
"Programação",
"Go",
"Backend"
],
"in_stock": true
}
=== Resultado da Deserialização ===
Título: Dominando Go
Autor: Maria Santos
Tags: [Go Avançado Concorrência]
Em Estoque: false
Exemplo: JSON Aninhado e Manipulação de Map (Dificuldade ⭐⭐)
package main
import (
"encoding/json"
"fmt"
"log"
)
// Struct Address
type Address struct {
City string `json:"city"`
Street string `json:"street"`
ZipCode string `json:"zip_code"`
}
// Informações de contato
type Contact struct {
Phone string `json:"phone"`
Email string `json:"email"`
}
// Struct Employee (com aninhamento)
type Employee struct {
Name string `json:"name"`
Age int `json:"age"`
Address Address `json:"address"` // Struct aninhado
Contact Contact `json:"contact"` // Struct aninhado
Skills []string `json:"skills"` // Slice
Metadata map[string]string `json:"metadata"` // Campos dinâmicos
}
func main() {
// Construir dados aninhados
emp := Employee{
Name: "Alice",
Age: 35,
Address: Address{
City: "São Paulo",
Street: "Rua Augusta, 1234",
ZipCode: "01310-100",
},
Contact: Contact{
Phone: "11987654321",
Email: "alice@example.com",
},
Skills: []string{"Go", "Python", "Docker"},
Metadata: map[string]string{
"department": "Engenharia",
"level": "P7",
"joined": "2020-03-15",
},
}
// Serializar
data, err := json.MarshalIndent(emp, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println("=== Serialização de JSON Aninhado ===")
fmt.Println(string(data))
// Lidar com JSON dinâmico (usando map)
dynamicJSON := `{
"event": "user_login",
"timestamp": 1700000000,
"data": {
"user_id": 12345,
"ip": "192.168.1.100",
"browser": "Chrome"
},
"tags": ["web", "auth"]
}`
var result map[string]interface{}
err = json.Unmarshal([]byte(dynamicJSON), &result)
if err != nil {
log.Fatal(err)
}
fmt.Println("\n=== Análise de JSON Dinâmico ===")
fmt.Printf("Evento: %s\n", result["event"])
fmt.Printf("Timestamp: %.0f\n", result["timestamp"])
// Acessar mapa aninhado
if data, ok := result["data"].(map[string]interface{}); ok {
fmt.Printf("ID do Usuário: %.0f\n", data["user_id"])
fmt.Printf("Endereço IP: %s\n", data["ip"])
}
// Acessar array
if tags, ok := result["tags"].([]interface{}); ok {
fmt.Print("Tags: ")
for _, tag := range tags {
fmt.Printf("%s ", tag)
}
fmt.Println()
}
}
Saída:
=== Serialização de JSON Aninhado ===
{
"name": "Alice",
"age": 35,
"address": {
"city": "São Paulo",
"street": "Rua Augusta, 1234",
"zip_code": "01310-100"
},
"contact": {
"phone": "11987654321",
"email": "alice@example.com"
},
"skills": [
"Go",
"Python",
"Docker"
],
"metadata": {
"department": "Engenharia",
"joined": "2020-03-15",
"level": "P7"
}
}
=== Análise de JSON Dinâmico ===
Evento: user_login
Timestamp: 1700000000
ID do Usuário: 12345
Endereço IP: 192.168.1.100
Tags: web auth
Exemplo: Serialização Personalizada e Streaming (Dificuldade ⭐⭐⭐)
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
"time"
)
// CustomTime tipo de tempo personalizado
type CustomTime struct {
time.Time
}
// Implementar interface json.Marshaler
func (ct CustomTime) MarshalJSON() ([]byte, error) {
// Formato de saída: 2006-01-02 15:04:05
formatted := ct.Format("2006-01-02 15:04:05")
return json.Marshal(formatted)
}
// Implementar interface json.Unmarshaler
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
// Suportar análise de múltiplos formatos
formats := []string{
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006/01/02",
}
for _, format := range formats {
t, err := time.Parse(format, s)
if err == nil {
ct.Time = t
return nil
}
}
return fmt.Errorf("não foi possível analisar o horário: %s", s)
}
// Status tipo enum personalizado
type Status int
const (
StatusActive Status = iota // 0
StatusInactive // 1
StatusBanned // 2
)
// Mapeamento Status para string
var statusNames = map[Status]string{
StatusActive: "active",
StatusInactive: "inactive",
StatusBanned: "banned",
}
// Mapeamento string para Status
var statusValues = map[string]Status{
"active": StatusActive,
"inactive": StatusInactive,
"banned": StatusBanned,
}
// MarshalJSON serialização personalizada
func (s Status) MarshalJSON() ([]byte, error) {
name, ok := statusNames[s]
if !ok {
return json.Marshal("unknown")
}
return json.Marshal(name)
}
// UnmarshalJSON deserialização personalizada
func (s *Status) UnmarshalJSON(data []byte) error {
var name string
if err := json.Unmarshal(data, &name); err != nil {
return err
}
val, ok := statusValues[name]
if !ok {
return fmt.Errorf("status desconhecido: %s", name)
}
*s = val
return nil
}
// EventLog entrada de log de evento
type EventLog struct {
Event string `json:"event"`
Timestamp CustomTime `json:"timestamp"`
Status Status `json:"status"`
Details string `json:"details,omitempty"`
}
func main() {
// === Demonstração de serialização personalizada ===
logEntry := EventLog{
Event: "user_register",
Timestamp: CustomTime{time.Date(2024, 1, 15, 14, 30, 0, 0, time.Local)},
Status: StatusActive,
Details: "Novo usuário registrado com sucesso",
}
data, _ := json.MarshalIndent(logEntry, "", " ")
fmt.Println("=== Serialização Personalizada ===")
fmt.Println(string(data))
// === Demonstração de deserialização personalizada ===
jsonStr := `{
"event": "user_login",
"timestamp": "2024/01/15",
"status": "inactive"
}`
var entry EventLog
err := json.Unmarshal([]byte(jsonStr), &entry)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nResultado da análise: Evento=%s, Hora=%s, Status=%d\n",
entry.Event,
entry.Timestamp.Format("2006-01-02 15:04:05"),
entry.Status,
)
// === Demonstração de streaming ===
fmt.Println("\n=== Streaming Decoder ===")
// Simular stream JSON recebido da rede
jsonStream := `[
{"name": "Alice", "score": 95},
{"name": "Bob", "score": 87},
{"name": "Charlie", "score": 92}
]`
decoder := json.NewDecoder(strings.NewReader(jsonStream))
// Ler token inicial
token, err := decoder.Token()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Token inicial: %v\n", token)
// Ler elementos do array um por um
type Student struct {
Name string `json:"name"`
Score int `json:"score"`
}
var students []Student
for decoder.More() {
var s Student
if err := decoder.Decode(&s); err != nil {
log.Fatal(err)
}
students = append(students, s)
}
for _, s := range students {
fmt.Printf("Estudante: %s, Nota: %d\n", s.Name, s.Score)
}
// === Demonstração de Streaming Encoder ===
fmt.Println("\n=== Streaming Encoder ===")
var buf strings.Builder
encoder := json.NewEncoder(&buf)
encoder.SetIndent("", " ")
// Codificar objetos individualmente
for _, s := range students {
if err := encoder.Encode(s); err != nil {
log.Fatal(err)
}
}
fmt.Println(buf.String())
}
Saída:
=== Serialização Personalizada ===
{
"event": "user_register",
"timestamp": "2024-01-15 14:30:00",
"status": "active",
"details": "Novo usuário registrado com sucesso"
}
Resultado da análise: Evento=user_login, Hora=2024-01-15 00:00:00, Status=1
=== Streaming Decoder ===
Token inicial: [
Estudante: Alice, Nota: 95
Estudante: Bob, Nota: 87
Estudante: Charlie, Nota: 92
=== Streaming Encoder ===
{
"name": "Alice",
"score": 95
}
{
"name": "Bob",
"score": 87
}
{
"name": "Charlie",
"score": 92
}
Cenários de Aplicação
Cenário 1: Wrapper de Resposta de API
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
// APIResponse estrutura unificada de resposta da API
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
// SuccessResponse resposta de sucesso
func SuccessResponse(w http.ResponseWriter, data interface{}) {
resp := APIResponse{
Code: 200,
Message: "success",
Data: data,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(resp)
}
// ErrorResponse resposta de erro
func ErrorResponse(w http.ResponseWriter, statusCode int, errMsg string) {
resp := APIResponse{
Code: statusCode,
Message: "error",
Error: errMsg,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(resp)
}
// UserHandler lida com requisições de usuário
func UserHandler(w http.ResponseWriter, r *http.Request) {
// Dados de usuário simulados
users := []map[string]interface{}{
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "user"},
{"id": 3, "name": "Charlie", "role": "user"},
}
SuccessResponse(w, users)
}
func main() {
// Resposta de API simulada
fmt.Println("=== Resposta de API Simulada ===")
// Resposta de sucesso
successResp := APIResponse{
Code: 200,
Message: "success",
Data: map[string]interface{}{
"id": 1,
"name": "Alice",
},
}
data, _ := json.MarshalIndent(successResp, "", " ")
fmt.Println("Resposta de sucesso:")
fmt.Println(string(data))
// Resposta de erro
errorResp := APIResponse{
Code: 404,
Message: "error",
Error: "Usuário não encontrado",
}
data, _ = json.MarshalIndent(errorResp, "", " ")
fmt.Println("\nResposta de erro:")
fmt.Println(string(data))
_ = log.Fatal // Evitar aviso de não uso
}
Saída:
=== Resposta de API Simulada ===
Resposta de sucesso:
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"name": "Alice"
}
}
Resposta de erro:
{
"code": 404,
"message": "error",
"error": "Usuário não encontrado"
}
Cenário 2: Leitura e Validação de Arquivo de Configuração
package main
import (
"encoding/json"
"fmt"
"log"
"os"
)
// DatabaseConfig configuração do banco de dados
type DatabaseConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
DBName string `json:"dbname"`
}
// ServerConfig configuração do servidor
type ServerConfig struct {
Host string `json:"host"`
Port int `json:"port"`
ReadTimeout int `json:"read_timeout"`
WriteTimeout int `json:"write_timeout"`
AllowOrigins []string `json:"allow_origins"`
}
// AppConfig configuração da aplicação
type AppConfig struct {
AppName string `json:"app_name"`
Debug bool `json:"debug"`
Server ServerConfig `json:"server"`
Database DatabaseConfig `json:"database"`
}
// Validate valida a configuração
func (c *AppConfig) Validate() error {
if c.AppName == "" {
return fmt.Errorf("app_name não pode ser vazio")
}
if c.Server.Port <= 0 || c.Server.Port > 65535 {
return fmt.Errorf("server.port deve estar entre 1-65535")
}
if c.Database.Host == "" {
return fmt.Errorf("database.host não pode ser vazio")
}
return nil
}
func main() {
// Conteúdo do arquivo de configuração simulado
configJSON := `{
"app_name": "GoWebApp",
"debug": true,
"server": {
"host": "0.0.0.0",
"port": 8080,
"read_timeout": 30,
"write_timeout": 30,
"allow_origins": ["http://localhost:3000", "https://example.com"]
},
"database": {
"host": "localhost",
"port": 3306,
"username": "root",
"password": "secret123",
"dbname": "myapp"
}
}`
// Analisar configuração
var config AppConfig
err := json.Unmarshal([]byte(configJSON), &config)
if err != nil {
log.Fatalf("Falha ao analisar configuração: %v", err)
}
// Validar configuração
if err := config.Validate(); err != nil {
log.Fatalf("Falha na validação da configuração: %v", err)
}
// Imprimir informações da configuração
fmt.Printf("Nome da App: %s\n", config.AppName)
fmt.Printf("Modo Debug: %v\n", config.Debug)
fmt.Printf("Endereço do Servidor: %s:%d\n", config.Server.Host, config.Server.Port)
fmt.Printf("Conexão com Banco de Dados: %s:%d/%s\n",
config.Database.Host,
config.Database.Port,
config.Database.DBName,
)
fmt.Printf("Origens Permitidas: %v\n", config.Server.AllowOrigins)
// Exemplo de escrita (salvar configuração modificada)
config.Debug = false
config.Server.Port = 9090
output, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println("\n=== Configuração Modificada ===")
fmt.Println(string(output))
// Em um projeto real, você gravaria no arquivo:
// os.WriteFile("config.json", output, 0644)
_ = os.WriteFile // Evitar aviso de não uso
}
Saída:
Nome da App: GoWebApp
Modo Debug: true
Endereço do Servidor: 0.0.0.0:8080
Conexão com Banco de Dados: localhost:3306/myapp
Origens Permitidas: [http://localhost:3000 https://example.com]
=== Configuração Modificada ===
{
"app_name": "GoWebApp",
"debug": false,
"server": {
"host": "0.0.0.0",
"port": 9090,
"read_timeout": 30,
"write_timeout": 30,
"allow_origins": [
"http://localhost:3000",
"https://example.com"
]
},
"database": {
"host": "localhost",
"port": 3306,
"username": "root",
"password": "secret123",
"dbname": "myapp"
}
}
❓ Perguntas Frequentes
P1: Por que os nomes dos campos JSON são maiúsculos?
Razão: Go só exporta campos com a primeira letra maiúscula, e json.Marshal usa o nome do campo como chave JSON por padrão.
Solução: Usar tags de struct para especificar nomes em minúsculas:
type User struct {
Name string `json:"name"` // "name" no JSON
Age int `json:"age"` // "age" no JSON
Email string `json:"email"` // "email" no JSON
}
P2: Como ignorar campos com valores vazios?
Use a tag omitempty:
type Request struct {
Name string `json:"name"`
Email string `json:"email,omitempty"` // Omitir quando string vazia
Age int `json:"age,omitempty"` // Omitir quando 0
Items []string `json:"items,omitempty"` // Omitir quando nil ou slice vazio
}
// Teste
req := Request{Name: "Alice"}
data, _ := json.Marshal(req)
fmt.Println(string(data))
// Saída: {"name":"Alice"} — email, age, items são todos omitidos
P3: Como lidar com problemas de precisão numérica no JSON?
O json.Unmarshal do Go analisa números JSON como float64 por padrão, o que perde precisão para inteiros grandes:
// Exemplo do problema
var result map[string]interface{}
json.Unmarshal([]byte(`{"id": 12345678901234567}`), &result)
fmt.Printf("%.0f\n", result["id"]) // Saída: 12345678901234568 (precisão perdida!)
// Solução: usar json.Number
decoder := json.NewDecoder(strings.NewReader(`{"id": 12345678901234567}`))
decoder.UseNumber()
decoder.Decode(&result)
id, _ := result["id"].(json.Number).Int64()
fmt.Println(id) // Saída: 12345678901234567 (correto)
P4: Como lidar com JSON de estrutura desconhecida?
Use map[string]interface{} ou json.RawMessage:
// Método 1: Usar map
var data map[string]interface{}
json.Unmarshal(jsonBytes, &data)
// Método 2: Usar json.RawMessage para análise diferida
type Message struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"` // Análise diferida
}
// Determinar como analisar Payload com base no campo Type
switch msg.Type {
case "user":
var user User
json.Unmarshal(msg.Payload, &user)
case "order":
var order Order
json.Unmarshal(msg.Payload, &order)
}
📖 Resumo
Esta lição cobriu o conteúdo central de processamento JSON em Go:
- Operações Básicas:
json.Marshalpara serialização ejson.Unmarshalpara deserialização - Tags de Struct: Usar
json:"name"para controlar nomes de campos,omitemptypara omitir valores vazios,-para ignorar campos - Manipulação Aninhada: Aninhamento de structs,
map[string]interface{}para JSON dinâmico - Serialização Personalizada: Implementar interfaces
Marshaler/Unmarshaler - Streaming:
json.Decoderejson.Encoderpara dados em stream - Aplicações Práticas: Wrappers de resposta de API, gerenciamento de arquivos de configuração
- Sempre verificar o tratamento de erros
- Passar ponteiros para deserialização
- Usar tags de struct para manter convenções de nomenclatura JSON
- Usar streaming para grandes volumes de dados
📝 Exercícios
Exercício 1: Prática Básica
Escreva um programa que define uma struct Student (com nome, idade e uma lista de notas), e implementa:
- Criar 3 objetos de estudante
- Serializar para um array JSON com impressão formatada
- Deserializar de volta para structs e imprimir as informações
Exercício 2: Prática Intermediária
Implementar um gerenciador simples de configuração JSON:
- Definir uma struct de configuração da aplicação (contendo servidor, banco de dados, logging, etc.)
- Implementar uma função
LoadConfig(filename)para ler configuração do arquivo - Implementar uma função
SaveConfig(filename, config)para salvar configuração no arquivo - Implementar validação de configuração
Exercício 3: Prática Avançada
Implementar um manipulador de mensagens JSON-RPC:
- Definir estruturas de requisição e resposta
- Usar
json.RawMessagepara análise diferida - Despachar para diferentes funções manipuladoras com base no nome do método da requisição
- Suportar processamento de requisições em lote
// Dica: Formato de requisição JSON-RPC
type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
ID interface{} `json:"id"`
}
type RPCResponse struct {
JSONRPC string `json:"jsonrpc"`
Result interface{} `json:"result,omitempty"`
Error *RPCError `json:"error,omitempty"`
ID interface{} `json:"id"`
}
Próxima Lição
Após completar esta lição, continue para a Lição 22: Serviços HTTP, onde aprenderemos como usar Go para construir servidores e clientes HTTP.



