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:

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

GO
import "encoding/json"

2. Serialização: Struct → JSON

GO
// 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"}
💡 Dica: json.Marshal retorna []byte, que precisa de conversão com string() para imprimir JSON legível.

3. Deserialização: JSON → Struct

GO
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
💡 Dica: O segundo parâmetro de Unmarshal deve ser um ponteiro, caso contrário as alterações não terão efeito.

4. Tags de Struct

GO
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
}
💡 Dica:

  • 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 ⭐)

GO
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)
}
▶ Experimente

Saída:

TEXT
=== 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 ⭐⭐)

GO
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()
    }
}
▶ Experimente

Saída:

TEXT
=== 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 ⭐⭐⭐)

GO
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())
}
▶ Experimente

Saída:

TEXT
=== 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

GO
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:

TEXT
=== 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

GO
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:

TEXT
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:

GO
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:

GO
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:

GO
// 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:

GO
// 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:

  1. Operações Básicas: json.Marshal para serialização e json.Unmarshal para deserialização
  2. Tags de Struct: Usar json:"name" para controlar nomes de campos, omitempty para omitir valores vazios, - para ignorar campos
  3. Manipulação Aninhada: Aninhamento de structs, map[string]interface{} para JSON dinâmico
  4. Serialização Personalizada: Implementar interfaces Marshaler/Unmarshaler
  5. Streaming: json.Decoder e json.Encoder para dados em stream
  6. Aplicações Práticas: Wrappers de resposta de API, gerenciamento de arquivos de configuração
💡 Pontos-chave:

  • 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:

  1. Criar 3 objetos de estudante
  2. Serializar para um array JSON com impressão formatada
  3. 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:

  1. Definir uma struct de configuração da aplicação (contendo servidor, banco de dados, logging, etc.)
  2. Implementar uma função LoadConfig(filename) para ler configuração do arquivo
  3. Implementar uma função SaveConfig(filename, config) para salvar configuração no arquivo
  4. Implementar validação de configuração

Exercício 3: Prática Avançada

Implementar um manipulador de mensagens JSON-RPC:

  1. Definir estruturas de requisição e resposta
  2. Usar json.RawMessage para análise diferida
  3. Despachar para diferentes funções manipuladoras com base no nome do método da requisição
  4. Suportar processamento de requisições em lote
GO
// 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.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%