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
osoferece operações com arquivos no nível de chamadas de sistema, enquanto o pacoteencoding/jsonpermite 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á
- Operações com arquivos do sistema operacional (criar/abrir/ler/gravar)
os.ReadFile/os.WriteFile: Leitura e gravação em uma única operação- bufio: leitura/gravação em buffer (linha por linha/arquivos grandes)
encoding/jsonOrganizar / Desorganizar- Processamento em tempo real de codificação/decodificação de JSON
- As tags JSON controlam o comportamento da serialização
- Criar uma ferramenta de exportação de banco de dados utilizando E/S de arquivos e JSON
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:
// 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
// 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:
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 |
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
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:
Hello, Go Files!
Second line
Appended line
(2) os.ReadFile / os.WriteFile (leitura/gravação única)
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
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)
}
Resultado:
Copied 13 bytes
Content: Hello World!
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
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:
Line 1
Line 2
Line 3
...total 100 lines
(2) ▶ Exemplo: gravação no buffer do bufio
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)
}
(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
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:
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{})
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"])
}
}
Resultado:
name: Alice
age: 28 (type=float64)
city: Shanghai
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
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))
}
(2) Decodificador: Leitura baseada em fluxo (JSON linha por linha)
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:
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)
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
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))
}
Resultado:
{
"code": 200,
"id": "9876543210123"
}
8. Exemplo completo: Ferramenta de exportação de banco de dados em JSON
// 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:
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"
},
...
]
}
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
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.ReadFileeioutil.ReadFile? R:os.ReadFileé um substituto introduzido no Go 1.16; ele funciona da mesma maneira.ioutil.ReadFilefoi descontinuado a partir do Go 1.19. Você deve usaros.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.Readsempre 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.Marshalejson.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 doEncoderé que ele opera em modo de streaming e não consome muita memória.
P: Como o
struct tagcontrola 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/jsondo 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), usejson.MarshalIndentouSetEscapeHTML(false).
P: Como faço para lidar com arquivos JSON grandes? R: Use
json.Decoderpara 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 usejson.Unmarshalpara ler o arquivo inteiro.
P: Como faço para analisar um JSON com estrutura desconhecida? R: Use
map[string]interface{}oujson.RawMessage(análise diferida).map[string]interface{}é flexível, mas requer asserções de tipo;json.RawMessagepreserva o JSON bruto para análise posterior.
P: Qual é a diferença entre
os.Createeos.OpenFile? R:os.Create(f)é equivalente aos.OpenFile(f, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)— ele trunca (sobrescreve) o arquivo caso ele já exista.os.OpenFileoferece mais modos (acrescentar, somente leitura, criar um novo arquivo etc.).
📖 Resumo
os.ReadFile/os.WriteFile: Leitura e gravação em arquivos pequenos em uma única operaçãobufio.Scannerprocessa arquivos grandes linha por linha, ebufio.Writerarmazena as gravações em buffer- O pacote
encoding/jsonlida automaticamente com a serialização e a desserialização, eliminando a necessidade de construir strings manualmente json.Marshalprocessa os dados na memória, enquantojson.Encodergrava os dados em um fluxojson.Decoderlê dados em um fluxo, o que o torna adequado para arquivos grandes- A tag JSON:
omitempty/-/stringcontrola o comportamento da serialização - A E/S de arquivos e o JSON são unificados por meio das interfaces
io.Reader/io.Writer
📝 Exercícios
-
Problema Básico (Dificuldade ⭐): Escreva um programa que utilize
os.ReadFilepara ler um arquivo de texto, conte o número de linhas, palavras e caracteres e grave os resultados em outro arquivo. Você deve utilizarstrings.Fields,bufio.Scannereos.WriteFile. -
Problema avançado (Dificuldade ⭐⭐): Implemente um gerenciador
TodoListque suporte a adição, listagem, preenchimento e exclusão de itens e utilizeencoding/jsonpara serializar dados em um arquivo para persistência. Requisitos: Use tags JSON para controlar os nomes dos campos e utilizeomitemptypara lidar com campos opcionais. -
Problema de desafio (Dificuldade ⭐⭐⭐): Implemente um exportador multiformato que exporte dados da fonte de dados
[]Productsimultaneamente em três formatos: JSON, JSON Lines (um objeto JSON por linha) e CSV. Você deve usaros.Create+json.Encoder(para JSON) /fmt.Fprintf(para CSV) e uma interface compartilhadaio.Writer.