Go: E/S de arquivos Go e processamento de JSON

Última atualização: 2026-08-26

E/S de arquivos e JSON são duas habilidades fundamentais no desenvolvimento de back-end em Go — o pacote os oferece operações com arquivos no nível de chamadas de sistema, enquanto o pacote encoding/json permite uma comunicação integrada entre o Go e o mundo do JavaScript.

As operações com arquivos e o processamento de JSON na biblioteca padrão do Go foram projetadas com uma abordagem inteligente e unificada: todas se baseiam nas interfaces io.Reader e io.Writer. Nesta lição, você vai dominar todas as habilidades essenciais para E/S de arquivos e processamento de JSON.

1. Você aprenderá


2. A história real de um engenheiro full-stack

(1) Problema: Concatenar manualmente strings JSON

Charlie é um engenheiro full-stack que precisa exportar dados de usuários do banco de dados para um arquivo JSON, a fim de que sejam utilizados pelo front-end:

“Não quis usar uma biblioteca de terceiros, então construí manualmente a string JSON. Acontece que havia um caractere \" no campo do nome de usuário, o que comprometeu totalmente o formato JSON. O erro só apareceu quando eu já estava na metade da exportação de 1 milhão de linhas de dados — e levei mais duas horas para reverter as alterações.”

Ele abriu o código que havia escrito:

GO
// Bad code: manually concatenating JSON strings
func exportUserJSON(users []User) string {
    result := "["
    for i, u := range users {
        if i > 0 {
            result += ","
        }
        // Manual concatenation, double quotes not escaped
        result += "{\"name\":\"" + u.Name + "\",\"age\":" + string(u.Age) + "}"
    }
    result += "]"
    return result
}

Se u.Name contiver " ou \, o JSON gerado ficará corrompido. Além disso, string(u.Age) converte números para ASCII — 28 passa a ser \x1c.

(2) Solução em Go: encoding/json + E/S de arquivos

GO
// json_exporter.go
package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    City string `json:"city"`
}

func main() {
    users := []User{
        {Name: "Alice", Age: 28, City: "Shanghai"},
        {Name: `Bob "The Builder"`, Age: 32, City: "Beijing"},
        {Name: "Charlie", Age: 25, City: "Guangzhou"},
    }

    // JSON serialization to file (no manual concatenation needed)
    file, _ := os.Create("users.json")
    defer file.Close()

    encoder := json.NewEncoder(file)
    encoder.SetIndent("", "  ")
    encoder.Encode(users)

    fmt.Println("Export successful!")

    // Verification: read it back
    data, _ := os.ReadFile("users.json")
    fmt.Println(string(data))
}

Resultado:

TEXT 📖 Somente leitura
Export successful!
[
  {
    "name": "Alice",
    "age": 28,
    "city": "Shanghai"
  },
  {
    "name": "Bob \"The Builder\"",
    "age": 32,
    "city": "Beijing"
  },
  {
    "name": "Charlie",
    "age": 25,
    "city": "Guangzhou"
  }
]

(3) Benefícios: Comparação do processamento de JSON

Método Escapamento de aspas duplas Caracteres especiais Arquivos grandes Volume de código
Concatenação manual ❌ Escapamento manual ❌ Propenso a erros ❌ OOM ~50 linhas
json.Marshal ✅ Automático ✅ Automático ❌ Memória cheia ~5 linhas
json.Encoder ✅ Automático ✅ Automático ✅ Baseado em fluxo ~5 linhas
💡 Dica: Nunca crie JSON manualmente — use o pacote encoding/json. Ele lida automaticamente com o escape, a codificação e a indentação, além de tratar corretamente os caracteres UTF-8 do Go.


3. Operações com arquivos no sistema operacional

(1) Abrindo e criando arquivos

GO
package main

import (
    "fmt"
    "os"
)

func main() {
    // Create (or truncate) file
    f, _ := os.Create("test.txt")
    defer f.Close()

    // Write string
    f.WriteString("Hello, Go Files!\n")
    f.Write([]byte("Second line\n"))

    // Open in append mode
    f2, _ := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY, 0644)
    defer f2.Close()
    f2.WriteString("Appended line\n")

    // Read file
    data, _ := os.ReadFile("test.txt")
    fmt.Print(string(data))
}

Resultado:

TEXT 📖 Somente leitura
Hello, Go Files!
Second line
Appended line

(2) os.ReadFile / os.WriteFile (leitura/gravação única)

GO
package main

import (
    "fmt"
    "os"
)

func main() {
    // Write (one-time)
    content := []byte("line1\nline2\nline3\n")
    err := os.WriteFile("data.txt", content, 0644)
    if err != nil {
        fmt.Printf("Write error: %v\n", err)
        return
    }

    // Read (one-time)
    data, err := os.ReadFile("data.txt")
    if err != nil {
        fmt.Printf("Read error: %v\n", err)
        return
    }
    fmt.Printf("Read %d bytes:\n%s", len(data), data)
}

(3) ▶ Exemplo: Como copiar um arquivo

GO
package main

import (
    "fmt"
    "io"
    "os"
)

func copyFile(src, dst string) (int64, error) {
    sourceFile, err := os.Open(src)
    if err != nil {
        return 0, err
    }
    defer sourceFile.Close()

    destFile, err := os.Create(dst)
    if err != nil {
        return 0, err
    }
    defer destFile.Close()

    // io.Copy uses a default 32KB buffer
    return io.Copy(destFile, sourceFile)
}

func main() {
    // First write the source file
    os.WriteFile("source.txt", []byte("Hello World!\n"), 0644)

    n, err := copyFile("source.txt", "dest.txt")
    if err != nil {
        fmt.Printf("Copy error: %v\n", err)
        return
    }
    fmt.Printf("Copied %d bytes\n", n)

    data, _ := os.ReadFile("dest.txt")
    fmt.Printf("Content: %s", data)
}
▶ Experimente

Resultado:

TEXT 📖 Somente leitura
Copied 13 bytes
Content: Hello World!
🔥 Erro comum: O os.ReadFile carrega o arquivo inteiro na memória — o que é adequado para arquivos pequenos (< 100 MB). Arquivos grandes (como logs com mais de 1 GB) devem ser processados usando o bufio ou o io.Copy para processamento em fluxo contínuo.


4. bufio: E/S com buffer

(1) O bufio lê linha por linha

GO
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    // First write a test file
    content := ""
    for i := 1; i <= 100; i++ {
        content += fmt.Sprintf("Line %d\n", i)
    }
    os.WriteFile("large.txt", []byte(content), 0644)

    // Read line by line
    file, _ := os.Open("large.txt")
    defer file.Close()

    scanner := bufio.NewScanner(file)
    lineCount := 0
    for scanner.Scan() {
        lineCount++
        if lineCount <= 3 {
            fmt.Println(scanner.Text())
        }
    }
    fmt.Printf("...total %d lines\n", lineCount)
}

Resultado:

TEXT 📖 Somente leitura
Line 1
Line 2
Line 3
...total 100 lines

(2) ▶ Exemplo: gravação no buffer do bufio

GO
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    file, _ := os.Create("buffered.txt")
    defer file.Close()

    writer := bufio.NewWriter(file)
    writer.WriteString("First line\n")
    writer.WriteString("Second line\n")
    writer.WriteString("Third line\n")

    fmt.Printf("Buffer size: %d\n", writer.Buffered())

    // Important: flush the buffer to disk
    writer.Flush()

    data, _ := os.ReadFile("buffered.txt")
    fmt.Printf("Content:\n%s", data)
}
▶ Experimente

(3) OS x Bufio

Recurso Leitura/gravação direta no sistema operacional Bufferização Bufio
Mecanismo interno Chamada de sistema (uma chamada de sistema por operação de leitura/gravação) Buffer no modo de usuário (reduz o número de chamadas de sistema)
Cenários adequados Arquivos pequenos, acesso aleatório Arquivos grandes, leitura/gravação sequencial
Ler linha por linha Não suportado bufio.Scanner
Desempenho (arquivos grandes) Lento (alto número de chamadas de sistema) Rápido (redução de 90% nas chamadas de sistema)
Buffer padrão N/A 4 KB (configurável)

5. Serialização com encoding/json

(1) json.Marshal / json.Unmarshal

GO
package main

import (
    "encoding/json"
    "fmt"
)

type Product struct {
    Name     string   `json:"name"`
    Price    float64  `json:"price"`
    InStock  bool     `json:"in_stock"`
    Tags     []string `json:"tags,omitempty"`
    Internal string   `json:"-"`  // not serialized
}

func main() {
    p := Product{
        Name:     "Go Mug",
        Price:    19.99,
        InStock:  true,
        Tags:     []string{"gift", "office"},
        Internal: "secret123",
    }

    // Serialize
    data, _ := json.Marshal(p)
    fmt.Printf("Marshaled: %s\n", data)

    // With indentation
    pretty, _ := json.MarshalIndent(p, "", "  ")
    fmt.Printf("Pretty:\n%s\n", pretty)

    // Deserialize
    var p2 Product
    json.Unmarshal([]byte(`{"name":"Go T-Shirt","price":29.99,"in_stock":false}`), &p2)
    fmt.Printf("Unmarshaled: %+v\n", p2)
}

Resultado:

TEXT 📖 Somente leitura
Marshaled: {"name":"Go Mug","price":19.99,"in_stock":true,"tags":["gift","office"]}
Pretty:
{
  "name": "Go Mug",
  "price": 19.99,
  "in_stock": true,
  "tags": [
    "gift",
    "office"
  ]
}
Unmarshaled: {Name:Go T-Shirt Price:29.99 InStock:false Tags:[] Internal:}

(2) ▶ Exemplo: Análise dinâmica de JSON (interface{})

GO
package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    data := `{"name":"Alice","age":28,"address":{"city":"Shanghai","zip":"200000"}}`

    // Parse into map (no need to pre-define a struct)
    var result map[string]interface{}
    json.Unmarshal([]byte(data), &result)

    fmt.Printf("name: %v\n", result["name"])
    fmt.Printf("age: %v (type=%T)\n", result["age"], result["age"])

    // Nested map requires type assertion (comma-ok safe pattern)
    if addr, ok := result["address"].(map[string]interface{}); ok {
        fmt.Printf("city: %v\n", addr["city"])
    }
}
▶ Experimente

Resultado:

TEXT 📖 Somente leitura
name: Alice
age: 28 (type=float64)
city: Shanghai
💡 Dica: Os números no JSON são interpretados como float64 por padrão — portanto, age é exibido como 28, mas seu tipo é float64. Se você quiser usar int, especifique o tipo usando uma estrutura ou use json.Decoder com UseNumber().


6. Processamento em streaming do codificador/decodificador JSON

(1) ▶ Exemplo: O codificador grava em um arquivo em tempo real

GO
package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    users := []User{
        {Name: "Alice", Age: 28},
        {Name: "Bob", Age: 32},
        {Name: "Charlie", Age: 25},
    }

    // Stream write to file (no complete []byte constructed)
    file, _ := os.Create("users.json")
    defer file.Close()

    encoder := json.NewEncoder(file)
    encoder.SetIndent("", "  ")

    for _, u := range users {
        encoder.Encode(u)  // write one by one
    }

    data, _ := os.ReadFile("users.json")
    fmt.Println(string(data))
}
▶ Experimente

(2) Decodificador: Leitura baseada em fluxo (JSON linha por linha)

GO
package main

import (
    "encoding/json"
    "fmt"
    "strings"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    // Simulate JSON Lines format (one JSON object per line)
    input := `{"name":"Alice","age":28}
{"name":"Bob","age":32}
{"name":"Charlie","age":25}`

    // Stream decode
    decoder := json.NewDecoder(strings.NewReader(input))
    for {
        var u User
        err := decoder.Decode(&u)
        if err != nil {
            break  // EOF
        }
        fmt.Printf("Decoded: %s (%d)\n", u.Name, u.Age)
    }
}

Resultado:

TEXT 📖 Somente leitura
Decoded: Alice (28)
Decoded: Bob (32)
Decoded: Charlie (25)

(3) Marshal x Encoder

Propriedade json.Marshal json.Encoder
Destino de saída []byte (memória) io.Writer (qualquer destino)
Uso de memória Compilação concluída []byte Buffer de gravação do fluxo
Arquivos grandes ❌ Risco de OOM ✅ Segurança
Controle de recuo MarshalIndent SetIndent()
Cenários típicos Respostas de API / Pequenos volumes de dados Gravação de arquivos / Fluxos de rede

7. O Guia Completo sobre Tags JSON

Opção de tag (1)

GO
type Config struct {
    Name     string   `json:"name"`               // field name mapping
    Omit     string   `json:"omit,omitempty"`      // omit on zero value
    Skip     string   `json:"-"`                   // skip this field
    String   int      `json:"string"`              // number converted to string
}

(2) Todas as opções

Opção Sintaxe Efeito
Renomear json:"new_name" Alterar o nome do campo JSON para “new_name”
omitempty json:"name,omitempty" Omita este campo se o valor for zero
Pular json:"-" Não serializar/desserializar
string json:"id,string" Converte números em strings (compatível com JavaScript)
Aninhamento json:"-" estrutura intermediária Controle da serialização aninhada

(3) ▶ Exemplo: omitempty + string na prática

GO
package main

import (
    "encoding/json"
    "fmt"
)

type APIResponse struct {
    Code    int    `json:"code"`
    Message string `json:"message,omitempty"`  // omitted when empty
    Data    any    `json:"data,omitempty"`      // omitted when nil
    ID      int64  `json:"id,string"`           // int64 → string
}

func main() {
    resp := APIResponse{
        Code: 200,
        Data: nil,     // will be omitted
        ID:   9876543210123,
    }

    data, _ := json.MarshalIndent(resp, "", "  ")
    fmt.Println(string(data))
}
▶ Experimente

Resultado:

TEXT 📖 Somente leitura
{
  "code": 200,
  "id": "9876543210123"
}

8. Exemplo completo: Ferramenta de exportação de banco de dados em JSON

GO
// db_exporter.go
package main

import (
    "encoding/json"
    "fmt"
    "os"
    "time"
)

// ---------- Data Model ----------

type Product struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Price     float64   `json:"price"`
    Stock     int       `json:"stock"`
    Category  string    `json:"category,omitempty"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at,omitempty"`
}

type ExportConfig struct {
    FilePath     string `json:"file_path"`
    PrettyPrint  bool   `json:"pretty_print"`
    IncludeMeta  bool   `json:"include_meta"`
}

// ---------- Simulated Database ----------

type Database struct {
    products []Product
}

func NewDatabase() *Database {
    return &Database{
        products: []Product{
            {ID: 1, Name: "Go Mug", Price: 19.99, Stock: 100, Category: "accessories",
                CreatedAt: time.Now()},
            {ID: 2, Name: `Go "Gopher" T-Shirt`, Price: 29.99, Stock: 50, Category: "clothing",
                CreatedAt: time.Now()},
            {ID: 3, Name: "Go Programming Book", Price: 49.99, Stock: 200, Category: "books",
                CreatedAt: time.Now()},
        },
    }
}

func (db *Database) QueryAll() []Product {
    return db.products
}

// ---------- Exporter ----------

type Exporter struct {
    config ExportConfig
}

func NewExporter(config ExportConfig) *Exporter {
    return &Exporter{config: config}
}

func (e *Exporter) ExportToFile(products []Product) error {
    file, err := os.Create(e.config.FilePath)
    if err != nil {
        return fmt.Errorf("create file: %w", err)
    }
    defer file.Close()

    encoder := json.NewEncoder(file)
    if e.config.PrettyPrint {
        encoder.SetIndent("", "  ")
    }

    if e.config.IncludeMeta {
        wrapper := map[string]interface{}{
            "exported_at": time.Now().Format(time.RFC3339),
            "total":       len(products),
            "products":    products,
        }
        return encoder.Encode(wrapper)
    }

    return encoder.Encode(products)
}

func (e *Exporter) ExportToMemory(products []Product) ([]byte, error) {
    if e.config.PrettyPrint {
        return json.MarshalIndent(products, "", "  ")
    }
    return json.Marshal(products)
}

// ---------- Importer (reads JSON file) ----------

type Importer struct{}

func NewImporter() *Importer {
    return &Importer{}
}

func (imp *Importer) ImportFromFile(path string) ([]Product, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("open file: %w", err)
    }
    defer file.Close()

    var products []Product
    decoder := json.NewDecoder(file)
    if err := decoder.Decode(&products); err != nil {
        // Try the wrapped format: must rebuild decoder after Seek
        file.Seek(0, 0)
        decoder = json.NewDecoder(file)
        var wrapped struct {
            Products []Product `json:"products"`
        }
        if err2 := decoder.Decode(&wrapped); err2 != nil {
            return nil, fmt.Errorf("decode: %w", err)
        }
        products = wrapped.Products
    }

    return products, nil
}

func main() {
    db := NewDatabase()
    products := db.QueryAll()

    // 1. Stream JSON write to file
    exporter := NewExporter(ExportConfig{
        FilePath:    "export.json",
        PrettyPrint: true,
        IncludeMeta: true,
    })
    if err := exporter.ExportToFile(products); err != nil {
        fmt.Printf("Export error: %v\n", err)
        return
    }
    fmt.Println("Exported to export.json")

    // 2. Read back to verify
    importer := NewImporter()
    imported, err := importer.ImportFromFile("export.json")
    if err != nil {
        fmt.Printf("Import error: %v\n", err)
        return
    }
    fmt.Printf("Imported %d products\n", len(imported))
    for _, p := range imported {
        fmt.Printf("  %d. %s ($%.2f)\n", p.ID, p.Name, p.Price)
    }

    // 3. Display file content
    data, _ := os.ReadFile("export.json")
    fmt.Printf("\nFile content:\n%s\n", data)
}

Resultado esperado:

TEXT 📖 Somente leitura
Exported to export.json
Imported 3 products
  1. Go Mug ($19.99)
  2. Go "Gopher" T-Shirt ($29.99)
  3. Go Programming Book ($49.99)

File content:
{
  "exported_at": "2026-07-08T10:00:00Z",
  "total": 3,
  "products": [
    {
      "id": 1,
      "name": "Go Mug",
      "price": 19.99,
      "stock": 100,
      "category": "accessories",
      "created_at": "2026-07-08T10:00:00Z"
    },
    ...
  ]
}
100%
flowchart LR
    A[Go struct] --> B[json.Marshal]
    A --> C[json.Encoder]
    B --> D["[]byte (memory)"]
    D --> E[os.WriteFile / HTTP Response]
    C --> F[File / net.Conn / bytes.Buffer]
    F --> G[Stream write]
    H[JSON file/stream] --> I[json.Decoder]
    I --> J[Go struct]
    H --> K[json.Unmarshal]
    K --> J
    style A fill:#e1f5fe
    style J fill:#e1f5fe
🔥 Erro comum: A opção omitempty durante a exportação em JSON não funciona para horários com valor zero (time.Time{}) — time.Time{} não é nil e não será omitido. Se você precisar omitir horários com valor zero, use o tipo ponteiro *time.Time.


❓ Perguntas Frequentes

P: Qual é a diferença entre os.ReadFile e ioutil.ReadFile? R: os.ReadFile é um substituto introduzido no Go 1.16; ele funciona da mesma maneira. ioutil.ReadFile foi descontinuado a partir do Go 1.19. Você deve usar os.ReadFile / os.WriteFile.

P: Por que o bufio é mais rápido do que as leituras e gravações diretas usando o sistema operacional? R: O bufio mantém um buffer (4 KB por padrão) no espaço do usuário, o que reduz significativamente o número de chamadas de sistema. Uma chamada direta os.File.Read sempre aciona uma chamada de sistema (com uma sobrecarga de cerca de 1 µs), enquanto o bufio lê 4 KB por vez, reduzindo as chamadas de sistema em 99,9%.

P: Como faço para escolher entre json.Marshal e json.Encoder? R: Para tamanhos de dados < 100 KB ou quando for necessário processamento em memória → Marshal; para gravação em arquivos/fluxos de rede ou arquivos grandes → Encoder. A vantagem do Encoder é que ele opera em modo de streaming e não consome muita memória.

P: Como o struct tag controla os campos JSON? R: json:"name" renomeia o campo; json:"name,omitempty" omite valores nulos; json:"-" ignora o campo; json:"id,string" converte números em strings. Separe várias opções com vírgulas.

P: Como a codificação não ASCII é tratada no JSON? R: O encoding/json do Go gera UTF-8 por padrão — os caracteres não ASCII são gerados tal como estão. Se você precisar de uma saída apenas em ASCII (conforme exigido por alguns sistemas legados), use json.MarshalIndent ou SetEscapeHTML(false).

P: Como faço para lidar com arquivos JSON grandes? R: Use json.Decoder para decodificação em streaming (decoder.Decode(&v) para decodificar um objeto por vez) ou o formato JSON Lines (um objeto JSON por linha). Não use json.Unmarshal para ler o arquivo inteiro.

P: Como faço para analisar um JSON com estrutura desconhecida? R: Use map[string]interface{} ou json.RawMessage (análise diferida). map[string]interface{} é flexível, mas requer asserções de tipo; json.RawMessage preserva o JSON bruto para análise posterior.

P: Qual é a diferença entre os.Create e os.OpenFile? R: os.Create(f) é equivalente a os.OpenFile(f, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) — ele trunca (sobrescreve) o arquivo caso ele já exista. os.OpenFile oferece mais modos (acrescentar, somente leitura, criar um novo arquivo etc.).


📖 Resumo


📝 Exercícios

  1. Problema Básico (Dificuldade ⭐): Escreva um programa que utilize os.ReadFile para ler um arquivo de texto, conte o número de linhas, palavras e caracteres e grave os resultados em outro arquivo. Você deve utilizar strings.Fields, bufio.Scanner e os.WriteFile.

  2. Problema avançado (Dificuldade ⭐⭐): Implemente um gerenciador TodoList que suporte a adição, listagem, preenchimento e exclusão de itens e utilize encoding/json para serializar dados em um arquivo para persistência. Requisitos: Use tags JSON para controlar os nomes dos campos e utilize omitempty para lidar com campos opcionais.

  3. Problema de desafio (Dificuldade ⭐⭐⭐): Implemente um exportador multiformato que exporte dados da fonte de dados []Product simultaneamente em três formatos: JSON, JSON Lines (um objeto JSON por linha) e CSV. Você deve usar os.Create + json.Encoder (para JSON) / fmt.Fprintf (para CSV) e uma interface compartilhada io.Writer.

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%