JSON Streaming para Arquivos Grandes: Processe Sem Carregar Tudo
O parsing padrão de JSON carrega o documento inteiro na memória, constrói uma estrutura de dados completa e depois dá acesso a você. Para um arquivo de 10 MB, funciona bem. Para um arquivo de 10 GB, seu processo fica sem memória e trava. Parsers de streaming resolvem isso processando o JSON incrementalmente — lendo e tratando dados conforme chegam, sem nunca manter o documento inteiro na memória.
O Problema do Parsing Padrão
import json
# Isso carrega o arquivo INTEIRO na memória
with open('huge.json') as f:
data = json.load(f) # Arquivo de 10 GB = 10+ GB de RAM
# O processamento acontece após o carregamento completo
for item in data['records']:
process(item)
Para um arquivo com 1 milhão de registros de 10 KB cada, o parsing padrão precisa de:
- Tamanho do arquivo: ~10 GB
- Memória para parsing: ~10 GB (a string bruta)
- Memória para estrutura de dados: ~15-20 GB (objetos Python são maiores que JSON bruto)
- Total: ~25-30 GB de RAM
Streaming reduz isso para megabytes.
Abordagens de Streaming
Estilo SAX (Baseado em Eventos)
O parser emite eventos conforme encontra tokens JSON:
import ijson
# Processa itens um de cada vez - uso de memória constante
with open('huge.json', 'rb') as f:
for record in ijson.items(f, 'records.item'):
process(record) # Cada registro é parseado individualmente
# Registros anteriores são coletados pelo garbage collector
Os eventos incluem: start_map, map_key, end_map, start_array, end_array, string, number, boolean, null.
JSON Lines (JSONL / NDJSON)
Uma abordagem mais simples: um objeto JSON por linha. Cada linha é um documento JSON completo e válido:
{"id": 1, "name": "Alice", "email": "alice@example.com"}
{"id": 2, "name": "Bob", "email": "bob@example.com"}
{"id": 3, "name": "Charlie", "email": "charlie@example.com"}
O processamento é trivial — leia linha por linha:
with open('data.jsonl') as f:
for line in f:
record = json.loads(line)
process(record)
Vantagens do JSON Lines:
- Cada linha é parseável independentemente (processamento paralelo)
- Amigável para adição (basta adicionar uma nova linha)
- Funciona com ferramentas padrão do Unix (
grep,wc,head,tail) - Natural para arquivos de log e dados de streaming
Processamento em Blocos
Para arrays JSON padrão, divida o processamento em blocos:
import ijson
def process_in_chunks(filename, chunk_size=1000):
chunk = []
with open(filename, 'rb') as f:
for record in ijson.items(f, 'item'):
chunk.append(record)
if len(chunk) >= chunk_size:
process_batch(chunk)
chunk = []
if chunk:
process_batch(chunk)
Implementações por Linguagem
Python (ijson)
import ijson
# Stream a partir de arquivo
with open('large.json', 'rb') as f:
parser = ijson.parse(f)
for prefix, event, value in parser:
if prefix == 'records.item.name':
print(value)
# Stream a partir de resposta HTTP
import urllib.request
response = urllib.request.urlopen('https://api.example.com/data')
for record in ijson.items(response, 'records.item'):
process(record)
JavaScript (Node.js)
const { createReadStream } = require('fs');
const { parser } = require('stream-json');
const { streamArray } = require('stream-json/streamers/StreamArray');
const pipeline = createReadStream('large.json')
.pipe(parser())
.pipe(streamArray());
pipeline.on('data', ({ value }) => {
process(value);
});
pipeline.on('end', () => {
console.log('Done processing');
});
Linha de Comando (jq)
# Modo stream - processa objetos individualmente
jq --stream 'select(length == 2) | .[1]' large.json
# Processa JSON Lines
cat data.jsonl | jq -c 'select(.age > 30)'
# Converte array para JSON Lines
jq -c '.[]' large_array.json > data.jsonl
Quando Usar Streaming
| Cenário | Padrão | Streaming |
|---|---|---|
| Arquivo abaixo de 100 MB | Preferível | Exagero |
| Arquivo de 100 MB a 1 GB | Depende da RAM | Recomendado |
| Arquivo acima de 1 GB | Inviável | Obrigatório |
| Resposta HTTP (grande) | Risco de timeout | Stream conforme recebido |
| Feed de dados em tempo real | Não aplicável | Obrigatório |
| Leitura simples e única | Preferível | Desnecessário |
Comparação de Desempenho
Processando 1 milhão de registros (arquivo de 1 GB):
| Abordagem | Uso de Memória | Tempo de Processamento | Complexidade |
|---|---|---|---|
| json.load() | 3-5 GB | 15 seg | Simples |
| ijson streaming | 50 MB | 45 seg | Moderada |
| JSON Lines | 10 MB | 12 seg | Simples |
| Em blocos (1000) | 100 MB | 20 seg | Moderada |
Streaming usa muito menos memória, mas é mais lento para parsing estilo SAX devido à sobrecarga baseada em eventos. JSON Lines é tanto rápido quanto eficiente em memória porque cada linha é uma operação de parse independente.
Boas Práticas
- Use JSON Lines quando possível: É o formato de streaming mais simples e funciona com ferramentas padrão.
- Configure tamanhos de buffer: Configure buffers de leitura para throughput ideal (64 KB a 1 MB tipicamente).
- Processe em lotes: Agrupe inserções no banco de dados e chamadas de API em vez de processar um registro por vez.
- Trate erros graciosamente: No streaming, um registro malformado não deve derrubar o pipeline inteiro.
- Monitore a memória: Use profiling para verificar se o streaming está realmente mantendo a memória limitada.
Para formatar e validar arquivos JSON menores, nosso Formatador JSON lida com documentos de até 100 MB com formatação em tempo real.
FAQ
Posso usar JSONPath com parsers de streaming?
Algumas bibliotecas de streaming suportam filtragem baseada em caminho. O ijson.items do Python suporta filtragem por caminho durante o streaming. No entanto, consultas JSONPath complexas (filtros, curingas entre níveis) tipicamente exigem o documento completo em memória. Para consultas baseadas em caminho, veja nosso guia de JSONPath.
Como converto um array JSON grande para JSON Lines?
Use jq -c '.[]' input.json > output.jsonl para arquivos que cabem na memória. Para arquivos realmente grandes, use um conversor de streaming: leia o array com um parser de streaming e escreva cada elemento como uma linha.
Recursos Relacionados
- Formatador JSON — Formate e valide arquivos JSON
- Guia de Consultas JSONPath — Extraia dados de JSON de forma eficiente
- Dicas para Editor JSON — Trabalhe com documentos JSON grandes