JSON para CSV em Python — Exemplos com DictWriter e pandas
Use o Conversor JSON para CSV gratuito diretamente no seu navegador — sem instalação.
Experimentar Conversor JSON para CSV online →Quase todo pipeline de dados chega ao mesmo passo eventualmente: uma API retorna JSON, mas o próximo consumidor — uma planilha, um script de importação, um comando Redshift COPY — precisa de CSV. Converter JSON para CSV em Python parece trivial até você se deparar com objetos aninhados, chaves inconsistentes ou valores datetime que precisam de tratamento especial. O Python oferece dois caminhos sólidos: os módulos embutidos json + csv para scripts sem dependências externas, e pandas para achatamento de dados aninhados e conjuntos de dados maiores — ou o conversor JSON para CSV online para conversões rápidas sem nenhum código. Este guia cobre ambas as abordagens do início ao fim, com exemplos executáveis em Python 3.8+.
- ✓csv.DictWriter converte uma lista de dicts para CSV sem dependências — use json.load() para analisar e depois writeheader() + writerows().
- ✓Sempre abra arquivos CSV com newline="" no Windows para evitar linhas em branco entre as linhas de dados.
- ✓pd.json_normalize() achata o JSON aninhado em um DataFrame simples antes de chamar to_csv() — trata aninhamentos de vários níveis automaticamente.
- ✓Passe index=False para DataFrame.to_csv() — sem ele, o pandas escreve uma coluna indesejada de números de linha.
- ✓Para arquivos acima de 500 MB, use ijson para análise de JSON em streaming combinada com csv.DictWriter para uso constante de memória.
O que é conversão de JSON para CSV?
A conversão de JSON para CSV transforma um array de objetos JSON em um formato tabular onde cada objeto se torna uma linha e cada chave se torna um cabeçalho de coluna. JSON é hierárquico — objetos podem ser aninhados arbitrariamente. CSV é plano — cada valor fica em uma grade de linha-coluna. A conversão funciona de forma limpa quando todos os objetos compartilham o mesmo conjunto de chaves de nível superior. Objetos aninhados, arrays e chaves inconsistentes são onde as coisas ficam interessantes. Os dados brutos permanecem idênticos; apenas a estrutura muda.
[{"order_id":"ord_91a3","total":149.99,"status":"shipped"},
{"order_id":"ord_b7f2","total":34.50,"status":"pending"}]order_id,total,status ord_91a3,149.99,shipped ord_b7f2,34.50,pending
csv.DictWriter — Converter JSON para CSV sem pandas
O módulo csv vem com toda instalação Python. Sem pip install, sem malabarismos com ambiente virtual. csv.DictWriter recebe uma lista de dicionários e escreve cada um como uma linha CSV, mapeando chaves do dict para cabeçalhos de coluna. O parâmetro fieldnames controla tanto a ordem das colunas quanto quais chaves são incluídas.
import json
import csv
# Dados JSON de exemplo — um array de objetos de pedido
json_string = """
[
{"order_id": "ord_91a3", "product": "Wireless Keyboard", "quantity": 2, "unit_price": 74.99},
{"order_id": "ord_b7f2", "product": "USB-C Hub", "quantity": 1, "unit_price": 34.50},
{"order_id": "ord_c4e8", "product": "Monitor Stand", "quantity": 3, "unit_price": 29.95}
]
"""
records = json.loads(json_string)
with open("orders.csv", "w", newline="", encoding="utf-8") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)
# orders.csv:
# order_id,product,quantity,unit_price
# ord_91a3,Wireless Keyboard,2,74.99
# ord_b7f2,USB-C Hub,1,34.50
# ord_c4e8,Monitor Stand,3,29.95O argumento newline="" no open() não é opcional no Windows. Sem ele, você obtém retornos de carro duplos — que aparecem como linhas em branco entre cada linha de dados no Excel. No macOS e Linux é inofensivo, então simplesmente sempre inclua.
O código acima usa json.loads() para uma string. Use json.load() (sem o s no final) ao ler de um identificador de arquivo. Isso confunde as pessoas constantemente — um lê uma string, o outro lê um objeto de arquivo.
import json
import csv
with open("server_metrics.json", encoding="utf-8") as jf:
metrics = json.load(jf) # json.load() para objetos de arquivo
# Fieldnames explícitos controlam a ordem das colunas
columns = ["timestamp", "hostname", "cpu_percent", "memory_mb", "disk_io_ops"]
with open("server_metrics.csv", "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=columns, extrasaction="ignore")
writer.writeheader()
writer.writerows(metrics)
# Apenas as cinco colunas especificadas aparecem, exatamente nessa ordemDefinir extrasaction="ignore" descarta silenciosamente quaisquer chaves nos dicts que não estão na sua lista de fieldnames. O padrão é "raise", que lança um ValueError se algum dict tiver uma chave inesperada. Escolha o que corresponde à sua tolerância a surpresas.
csv.DictWriter vs csv.writer: DictWriter mapeia as chaves do dict para posições de coluna automaticamente. csv.writer escreve listas brutas como linhas — você mesmo gerencia a ordenação das colunas. DictWriter é quase sempre a escolha certa para JSON-para-CSV porque os registros JSON já são dicionários.O módulo csv do Python vem com três dialetos nomeados: excel (delimitador de vírgula, terminações de linha CRLF — o padrão), excel-tab (delimitador de tabulação, terminações CRLF) e unix (terminações LF, coloca aspas em todos os campos não numéricos). Passe o nome do dialeto como argumento dialect para csv.DictWriter. Você também pode definir um dialeto personalizado com csv.register_dialect() quando o sistema de destino tiver regras incomuns de aspas ou delimitadores. Para a maioria dos fluxos de trabalho JSON-para-CSV, o dialeto excel é o correto, mas mude para unix ao escrever arquivos que serão processados por ferramentas POSIX como awk ou sort.
Tratando tipos não padrão: datetime, UUID e Decimal
JSON de APIs frequentemente contém datas como strings ISO, UUIDs como strings com hífen e valores monetários como floats. Quando você analisa esses dados em objetos Python para processamento antes de escrever o CSV, você precisa convertê-los de volta para strings. O módulo csv chama str() em cada valor, então a maioria dos tipos funciona direto. Mas objetos datetime produzem representações de string padrão confusas, e valores Decimal precisam de formatação explícita para evitar notação científica.
import json
import csv
from datetime import datetime, timezone
from decimal import Decimal
from uuid import UUID
# Simulando resposta de API analisada com tipos Python
transactions = [
{
"txn_id": UUID("a1b2c3d4-e5f6-7890-abcd-ef1234567890"),
"created_at": datetime(2026, 3, 15, 9, 30, 0, tzinfo=timezone.utc),
"amount": Decimal("1249.99"),
"currency": "USD",
"merchant": "CloudHost Inc.",
},
{
"txn_id": UUID("b2c3d4e5-f6a7-8901-bcde-f12345678901"),
"created_at": datetime(2026, 3, 15, 14, 12, 0, tzinfo=timezone.utc),
"amount": Decimal("87.50"),
"currency": "EUR",
"merchant": "DataSync GmbH",
},
]
def prepare_row(record: dict) -> dict:
"""Converte tipos não-string para strings compatíveis com CSV."""
return {
"txn_id": str(record["txn_id"]),
"created_at": record["created_at"].isoformat(),
"amount": f"{record['amount']:.2f}",
"currency": record["currency"],
"merchant": record["merchant"],
}
with open("transactions.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["txn_id", "created_at", "amount", "currency", "merchant"])
writer.writeheader()
for txn in transactions:
writer.writerow(prepare_row(txn))
# transactions.csv:
# txn_id,created_at,amount,currency,merchant
# a1b2c3d4-e5f6-7890-abcd-ef1234567890,2026-03-15T09:30:00+00:00,1249.99,USD,CloudHost Inc.
# b2c3d4e5-f6a7-8901-bcde-f12345678901,2026-03-15T14:12:00+00:00,87.50,EUR,DataSync GmbHA função prepare_row() é a abordagem correta aqui. Em vez de tentar ensinar csv.DictWriter sobre tipos customizados, você normaliza cada registro para strings antes de escrever. Prefiro chamar .isoformat() explicitamente em objetos datetime em vez de depender de str() — o formato de saída é mais previsível, e os parsers downstream lidam com ISO 8601 de forma confiável.
Decimal passarem sem formatação, números muito pequenos ou muito grandes podem ser renderizados em notação científica (ex.: 1.5E+7). Sempre formate Decimal com uma f-string explícita como f"{value:.2f}" ao escrever dados financeiros em CSV.Um padrão alternativo para pipelines com muitos tipos customizados é estender json.JSONEncoder. Crie uma subclasse, sobrescreva o método default() para retornar um valor serializável em JSON para cada tipo customizado, e passe a subclasse como argumento cls para json.dumps(). Recodificar pelo encoder customizado antes de escrever no CSV normaliza todos os tipos em uma etapa sem uma chamada prepare_row() por linha. O padrão prepare_row() mostrado acima é mais simples para scripts pontuais; a abordagem de subclasse JSONEncoder escala melhor quando o mesmo modelo de domínio com tipos customizados é compartilhado entre muitos estágios de pipeline ou microsserviços.
Referência de parâmetros do csv.DictWriter
A assinatura completa do construtor é csv.DictWriter(f, fieldnames, restval="", extrasaction="raise", dialect="excel", **fmtparams). A maioria tem padrões sensatos. Os que você realmente vai alterar são fieldnames, delimiter, e extrasaction.
pandas — Converter JSON para CSV com DataFrames
Se você já trabalha em uma base de código intensiva em pandas, ou seu JSON tem objetos aninhados que você precisa achatar, a abordagem pandas tem significativamente menos código do que a versão stdlib. A contrapartida: pandas é uma dependência de ~30 MB. Para um script descartável, isso é aceitável. Para uma imagem Docker que você envia para produção, a abordagem stdlib mantém as coisas mais leves.
import pandas as pd
# Lê o array JSON diretamente em um DataFrame
df = pd.read_json("warehouse_inventory.json")
# Escreve no CSV — index=False evita os números de linha gerados automaticamente
df.to_csv("warehouse_inventory.csv", index=False)
# É isso. Duas linhas. O pandas infere os tipos de coluna automaticamente.O flag index=False é uma daquelas coisas que você consulta toda vez. Sem ele, o pandas escreve uma coluna 0, 1, 2, ... como primeira coluna do seu CSV. Ninguém quer isso.
Achatando JSON aninhado com json_normalize
Respostas reais de API raramente são simples. Pedidos contêm endereços de envio, usuários contêm preferências aninhadas, eventos de telemetria contêm metadados aninhados. pd.json_normalize() percorre dicionários aninhados e os achata em colunas com nomes separados por ponto.
import json
import pandas as pd
api_response = """
[
{
"order_id": "ord_91a3",
"placed_at": "2026-03-15T09:30:00Z",
"customer": {
"name": "Sarah Chen",
"email": "s.chen@example.com",
"tier": "premium"
},
"shipping": {
"method": "express",
"address": {
"city": "Portland",
"state": "OR",
"zip": "97201"
}
},
"total": 299.95
},
{
"order_id": "ord_b7f2",
"placed_at": "2026-03-15T14:12:00Z",
"customer": {
"name": "James Park",
"email": "j.park@example.com",
"tier": "standard"
},
"shipping": {
"method": "standard",
"address": {
"city": "Austin",
"state": "TX",
"zip": "73301"
}
},
"total": 87.50
}
]
"""
orders = json.loads(api_response)
# json_normalize achata os dicts aninhados — sep controla o delimitador
df = pd.json_normalize(orders, sep="_")
df.to_csv("flat_orders.csv", index=False)
# Colunas resultantes:
# order_id, placed_at, customer_name, customer_email, customer_tier,
# shipping_method, shipping_address_city, shipping_address_state,
# shipping_address_zip, totalO parâmetro sep="_" controla como os nomes de chaves aninhadas são unidos. O padrão é ".", que produz colunas como customer.name. Prefiro sublinhados porque pontos em nomes de colunas causam problemas com importações SQL e algumas fórmulas de planilhas.
Para respostas de API que encapsulam o array de registros sob uma chave aninhada, use o parâmetro record_path. Se a resposta parecer {"data": {"orders": [...]}}, passe record_path=["data", "orders"] para navegar até a lista correta. O parâmetro opcional meta permite que você extraia campos do nível pai junto com os registros aninhados — útil quando a resposta inclui informações de paginação de nível superior (número da página, contagem total) que você deseja como coluna em cada linha. Juntos, record_path e meta lidam com a maioria das formas de resposta de API aninhadas do mundo real sem pré-processamento customizado.
Referência de parâmetros de DataFrame.to_csv()
DataFrame.to_csv() tem mais de 20 parâmetros. Estes são os que importam para fluxos de trabalho JSON-para-CSV.
import pandas as pd
df = pd.read_json("telemetry_events.json")
# Saída TSV com codificação explícita e tratamento de valores ausentes
df.to_csv(
"telemetry_events.tsv",
sep="\t",
index=False,
encoding="utf-8",
na_rep="NULL",
columns=["event_id", "timestamp", "source", "severity", "message"],
)
# Escreve no stdout para redirecionamento em scripts shell
print(df.to_csv(index=False))
# Retorna como string (nenhum arquivo escrito)
csv_string = df.to_csv(index=False)
print(len(csv_string), "characters")Converter JSON para CSV de um arquivo e resposta de API
Os dois cenários mais comuns no mundo real: ler JSON de um arquivo em disco e convertê-lo, ou buscar JSON de uma API HTTP e salvar o resultado como CSV. No desenvolvimento você pode se virar sem tratamento de erros. Em produção, essa escolha vira um alerta às 2 da manhã. Arquivos podem não existir, APIs podem retornar códigos de status 4xx ou 5xx em vez de JSON, o corpo da resposta pode ser um objeto de erro em vez de um array, ou o JSON pode estar truncado devido a um timeout de rede. Os padrões abaixo tratam todos esses casos explicitamente, registram erros no stderr e retornam uma contagem de linhas para que os chamadores possam detectar saídas com zero linhas e alertar adequadamente.
Arquivo em disco — Ler, converter, salvar
import json
import csv
import sys
def json_file_to_csv(input_path: str, output_path: str) -> int:
"""Converte um arquivo JSON contendo um array de objetos para CSV.
Retorna o número de linhas escritas.
"""
try:
with open(input_path, encoding="utf-8") as jf:
data = json.load(jf)
except FileNotFoundError:
print(f"Error: {input_path} not found", file=sys.stderr)
return 0
except json.JSONDecodeError as exc:
print(f"Error: invalid JSON in {input_path}: {exc.msg} at line {exc.lineno}", file=sys.stderr)
return 0
if not isinstance(data, list) or not data:
print(f"Error: expected a non-empty JSON array in {input_path}", file=sys.stderr)
return 0
# Coleta todas as chaves únicas de todos os registros — trata esquemas inconsistentes
all_keys: list[str] = []
seen: set[str] = set()
for record in data:
for key in record:
if key not in seen:
all_keys.append(key)
seen.add(key)
with open(output_path, "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=all_keys, restval="", extrasaction="ignore")
writer.writeheader()
writer.writerows(data)
return len(data)
rows = json_file_to_csv("deploy_logs.json", "deploy_logs.csv")
print(f"Wrote {rows} rows to deploy_logs.csv")Resposta de API HTTP — Buscar e converter
import json
import csv
import urllib.request
import urllib.error
def api_response_to_csv(url: str, output_path: str) -> int:
"""Busca JSON de um endpoint de API REST e o escreve como CSV."""
try:
req = urllib.request.Request(url, headers={"Accept": "application/json"})
with urllib.request.urlopen(req, timeout=30) as resp:
if resp.status != 200:
print(f"Error: API returned status {resp.status}")
return 0
body = resp.read().decode("utf-8")
except urllib.error.URLError as exc:
print(f"Error: could not reach {url}: {exc.reason}")
return 0
try:
records = json.loads(body)
except json.JSONDecodeError as exc:
print(f"Error: API returned invalid JSON: {exc.msg}")
return 0
if not isinstance(records, list) or not records:
print("Error: expected a non-empty JSON array from the API")
return 0
with open(output_path, "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)
return len(records)
rows = api_response_to_csv(
"https://api.internal.example.com/v2/deployments?status=completed",
"completed_deployments.csv",
)
print(f"Exported {rows} deployments to CSV")urllib da biblioteca padrão para manter o script sem dependências. Se você tiver requests instalado, substitua a seção urllib por resp = requests.get(url, timeout=30); records = resp.json() — o restante do código de escrita CSV permanece idêntico.Conversão de JSON para CSV pela linha de comando
Às vezes você só precisa de um comando rápido no terminal. O flag -c do Python permite executar uma conversão rápida sem criar um arquivo de script. Para transformações mais complexas, redirecione por jq primeiro para remodelar os dados e depois converta.
# Uma linha Python: lê JSON do stdin, escreve CSV no stdout cat orders.json | python3 -c " import json, csv, sys data = json.load(sys.stdin) w = csv.DictWriter(sys.stdout, fieldnames=data[0].keys()) w.writeheader() w.writerows(data) " # Salva a saída em um arquivo cat orders.json | python3 -c " import json, csv, sys data = json.load(sys.stdin) w = csv.DictWriter(sys.stdout, fieldnames=data[0].keys()) w.writeheader() w.writerows(data) " > orders.csv
# Salve como json2csv.py e execute: python3 json2csv.py input.json -o output.csv
python3 -c "
import json, csv, argparse, sys
parser = argparse.ArgumentParser(description='Convert JSON array to CSV')
parser.add_argument('input', help='Path to JSON file')
parser.add_argument('-o', '--output', default=None, help='Output CSV path (default: stdout)')
parser.add_argument('-d', '--delimiter', default=',', help='CSV delimiter')
args = parser.parse_args()
with open(args.input) as f:
data = json.load(f)
out = open(args.output, 'w', newline='') if args.output else sys.stdout
writer = csv.DictWriter(out, fieldnames=data[0].keys(), delimiter=args.delimiter)
writer.writeheader()
writer.writerows(data)
if args.output:
out.close()
print(f'Wrote {len(data)} rows to {args.output}', file=sys.stderr)
" "$@"# Instale csvkit: pip install csvkit
# jq seleciona e achata campos, in2csv cuida da formatação CSV
cat api_response.json | jq '[.[] | {id: .order_id, customer: .customer.name, total}]' | in2csv -f json > orders.csv
# Miller (mlr) é outra opção para JSON-para-CSV
mlr --json2csv cat orders.json > orders.csvMiller (mlr) é um binário independente que trata JSON, CSV e TSV como formatos de primeira classe sem necessidade de runtime Python. O flag --json2csv converte entrada JSON para CSV em uma única passagem, e você pode encadear verbos Miller para filtrar, ordenar ou renomear colunas no mesmo comando antes de escrever a saída. Instale via Homebrew no macOS (brew install miller) ou pelo gerenciador de pacotes do Linux. É particularmente útil em pipelines de CI onde você quer conversão rápida de JSON para CSV sem inicializar um ambiente Python.
Alternativa de alto desempenho — pandas com pyarrow
Para conjuntos de dados na faixa de dezenas de milhões de linhas, pandas com o backend pyarrow lê e escreve significativamente mais rápido do que o padrão. O motor Arrow baseado em C processa dados colunar de forma mais eficiente do que o módulo csv linha a linha do Python. A API permanece a mesma — você apenas define o parâmetro engine.
pip install pyarrow
import pandas as pd
# Lê JSON com motor pyarrow (análise mais rápida para arquivos grandes)
df = pd.read_json("sensor_readings.json", engine="pyarrow")
# to_csv não tem parâmetro engine, mas as operações do DataFrame
# entre leitura e escrita se beneficiam do layout colunar do pyarrow
df.to_csv("sensor_readings.csv", index=False)
# Para exportações realmente grandes, considere escrever em Parquet em vez de CSV
# — formato binário, 5-10x menor, preserva os tipos
df.to_parquet("sensor_readings.parquet", engine="pyarrow")Se você está processando mais do que algumas centenas de MB de JSON e o consumidor final aceita Parquet, pule o CSV completamente. Parquet é menor, preserva os tipos de coluna, e tanto o Redshift quanto o BigQuery o carregam nativamente. CSV é um formato com perda — cada valor se torna uma string.
Saída no terminal com destaque de sintaxe
A biblioteca rich renderiza tabelas com bordas, alinhamento e cor no terminal — útil para visualizar uma conversão durante o desenvolvimento sem abrir o arquivo de saída.
pip install rich
import json
from rich.console import Console
from rich.table import Table
json_string = """
[
{"hostname": "web-prod-1", "cpu_percent": 72.3, "memory_mb": 3840, "uptime_hours": 720},
{"hostname": "web-prod-2", "cpu_percent": 45.1, "memory_mb": 2560, "uptime_hours": 168},
{"hostname": "db-replica-1", "cpu_percent": 91.7, "memory_mb": 7680, "uptime_hours": 2160}
]
"""
records = json.loads(json_string)
console = Console()
table = Table(title="Visualização de Métricas do Servidor", show_lines=True)
for key in records[0]:
table.add_column(key, style="cyan" if key == "hostname" else "white")
for row in records:
table.add_row(*[str(v) for v in row.values()])
console.print(table)
# Renderiza uma tabela com destaque de cores e bordas no terminalcsv.DictWriter ou DataFrame.to_csv(), e use rich apenas para visualização.Trabalhando com arquivos JSON grandes
json.load() lê o arquivo inteiro na memória. Para um arquivo JSON de 200 MB, isso significa ~200 MB de texto bruto mais a sobrecarga do objeto Python — facilmente 500 MB+ de uso de heap. Para arquivos acima de 100 MB, transmita a entrada com ijson e escreva as linhas CSV conforme avança.
pip install ijson
Transmitindo array JSON para CSV com ijson
import ijson
import csv
def stream_json_to_csv(json_path: str, csv_path: str) -> int:
"""Converte um array JSON grande para CSV sem carregá-lo todo na memória."""
with open(json_path, "rb") as jf, open(csv_path, "w", newline="", encoding="utf-8") as cf:
# ijson.items produz cada elemento do array de nível superior um por vez
records = ijson.items(jf, "item")
first_record = next(records)
fieldnames = list(first_record.keys())
writer = csv.DictWriter(cf, fieldnames=fieldnames)
writer.writeheader()
writer.writerow(first_record)
count = 1
for record in records:
writer.writerow(record)
count += 1
return count
rows = stream_json_to_csv("clickstream_2026_03.json", "clickstream_2026_03.csv")
print(f"Streamed {rows} records to CSV")NDJSON / JSON Lines — Um objeto por linha
NDJSON (JSON delimitado por nova linha), também chamado de JSON Lines ou .jsonl, armazena um objeto JSON válido por linha sem array envolvente. Esse formato é comum em pipelines de log, streams de eventos (Kafka, Kinesis) e exportações em massa de serviços como Elasticsearch e BigQuery. Como cada linha é um objeto JSON independente, você pode processar um arquivo NDJSON com um simples loop for do Python sobre o identificador de arquivo — sem necessidade da biblioteca ijson. A memória permanece constante independentemente do tamanho do arquivo, tornando esta a abordagem de streaming mais simples quando seus dados de origem já estão no formato JSON Lines.
import json
import csv
def ndjson_to_csv(ndjson_path: str, csv_path: str) -> int:
"""Converte um arquivo JSON delimitado por nova linha para CSV, uma linha por vez."""
with open(ndjson_path, encoding="utf-8") as nf:
first_line = nf.readline()
first_record = json.loads(first_line)
fieldnames = list(first_record.keys())
with open(csv_path, "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=fieldnames)
writer.writeheader()
writer.writerow(first_record)
count = 1
for line in nf:
line = line.strip()
if not line:
continue
try:
record = json.loads(line)
writer.writerow(record)
count += 1
except json.JSONDecodeError:
continue # pula linhas malformadas
return count
rows = ndjson_to_csv("access_log.ndjson", "access_log.csv")
print(f"Converted {rows} log entries to CSV")json.load() pode consumir 3–5 GB de RAM devido à sobrecarga de objetos Python. Com ijson, a memória permanece plana independentemente do tamanho do arquivo. Se você só precisa de uma conversão rápida de um arquivo pequeno, cole-o no conversor JSON para CSV em vez disso.Erros comuns
Problema: O módulo csv escreve terminações de linha \r\n. Sem newline='', o modo texto do Python adiciona outro \r no Windows, produzindo saída com espaço duplo.
Correção: Sempre passe newline='' ao abrir um arquivo para escrita CSV. É inofensivo no macOS/Linux.
with open("output.csv", "w") as f:
writer = csv.DictWriter(f, fieldnames=columns)
writer.writeheader()
writer.writerows(data)
# Linhas em branco entre cada linha de dados no Windowswith open("output.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=columns)
writer.writeheader()
writer.writerows(data)
# Saída limpa em todas as plataformasProblema: Sem index=False, o pandas antecede uma coluna de número de linha auto-incremental (0, 1, 2, ...) que polui o CSV com dados que nunca estavam no JSON original.
Correção: Passe index=False para to_csv(). Se você realmente precisar de uma coluna de índice, nomeie-a explicitamente com df.index.name = 'row_num'.
df = pd.read_json("events.json")
df.to_csv("events.csv")
# CSV recebe uma coluna extra sem nome: ,event_id,timestamp,...
# A vírgula inicial quebra muitos parsers CSVdf = pd.read_json("events.json")
df.to_csv("events.csv", index=False)
# CSV limpo: event_id,timestamp,...Problema: Se os objetos JSON têm chaves diferentes (alguns registros têm campos opcionais), usar as chaves do primeiro registro como fieldnames descarta silenciosamente colunas que só aparecem em registros posteriores.
Correção: Colete todas as chaves únicas de todos os registros antes de criar o DictWriter.
records = json.load(f) writer = csv.DictWriter(out, fieldnames=records[0].keys()) # Perde o campo "discount" que só aparece em records[2]
records = json.load(f) all_keys = list(dict.fromkeys(k for r in records for k in r)) writer = csv.DictWriter(out, fieldnames=all_keys, restval="") # Cada chave de cada registro é incluída como coluna
Problema: csv.DictWriter chama str() em dicts aninhados, produzindo colunas com valores como "{'city': 'Portland'}"— repr bruto do Python, não dados reais.
Correção: Achate os objetos aninhados primeiro usando pd.json_normalize() ou uma função de achatamento customizada.
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
writer = csv.DictWriter(f, fieldnames=["id", "meta"])
writer.writerows(records)
# coluna meta contém: {'source': 'web', 'region': 'us-west'}import pandas as pd
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
df = pd.json_normalize(records, sep="_")
df.to_csv("events.csv", index=False)
# Colunas: id, meta_source, meta_regioncsv.DictWriter vs pandas — Comparação rápida
Use csv.DictWriter quando você precisar de zero dependências, seu JSON for simples e o script rodar em um ambiente restrito (contêineres de CI, funções Lambda, Python embarcado). Use pd.json_normalize() + to_csv() quando o JSON for aninhado, você precisar transformar ou filtrar dados antes da exportação, ou já estiver em um fluxo de trabalho pandas. Para arquivos que não cabem na memória, combine ijson com csv.DictWriter para streaming com memória constante.
Para conversões rápidas sem código, o conversor JSON para CSV no ToolDeck resolve sem nenhuma configuração Python.
Perguntas frequentes
Como converter JSON para CSV em Python sem pandas?
Use os módulos embutidos json e csv. Chame json.load() para analisar o arquivo JSON em uma lista de dicts, extraia os fieldnames das chaves do primeiro dict, crie um csv.DictWriter, chame writeheader() e depois writerows(). Essa abordagem não possui dependências externas e funciona em qualquer ambiente Python 3.x. Também é mais rápida que o pandas para arquivos pequenos, pois não há sobrecarga de alocação de DataFrame. Se os seus objetos JSON tiverem chaves inconsistentes entre os registros, colete primeiro todas as chaves únicas com dict.fromkeys(k for r in records for k in r) antes de passá-las como fieldnames para evitar colunas faltando.
import json
import csv
with open("orders.json") as f:
records = json.load(f)
with open("orders.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)Como lidar com JSON aninhado ao converter para CSV?
Arrays JSON simples mapeiam diretamente para linhas CSV, mas objetos aninhados precisam ser achatados primeiro. Com pandas, pd.json_normalize() faz isso automaticamente — ele une chaves aninhadas com um separador de ponto (ex.: "address.city"). Sem pandas, escreva uma função recursiva que percorre o dict e concatena chaves com um delimitador. Para estruturas profundamente aninhadas com múltiplos níveis, json_normalize lida com todos eles em uma única passagem. O parâmetro sep controla o caractere de junção entre segmentos de chave — o sublinhado geralmente é mais seguro do que o ponto padrão para importações SQL e compatibilidade com fórmulas de planilhas.
import pandas as pd
nested_data = [
{"id": "ord_91a3", "customer": {"name": "Ana Silva", "email": "a.silva@exemplo.com"}},
]
df = pd.json_normalize(nested_data, sep="_")
# Colunas: id, customer_name, customer_email
df.to_csv("flat_orders.csv", index=False)Por que meu CSV tem linhas em branco entre as linhas de dados no Windows?
O módulo csv escreve terminações de linha \r\n por padrão. No Windows, abrir o arquivo em modo texto adiciona outro \r, produzindo \r\r\n — o que aparece como uma linha em branco. A correção é sempre passar newline="" para open(). Isso instrui o Python a não traduzir as terminações de linha, deixando o módulo csv cuidar delas. Esse padrão é obrigatório independentemente do sistema operacional — é inofensivo no macOS e Linux, e crítico no Windows. A documentação do Python menciona explicitamente isso na seção do módulo csv como a forma correta de abrir arquivos para escrita CSV.
# Errado — linhas em branco no Windows
with open("output.csv", "w") as f:
writer = csv.writer(f)
# Correto — newline="" previne o \r duplo
with open("output.csv", "w", newline="") as f:
writer = csv.writer(f)Como anexar registros JSON a um arquivo CSV existente?
Abra o arquivo em modo de anexação ("a") e crie um DictWriter com os mesmos fieldnames. Pule writeheader() pois a linha de cabeçalho já existe. Com pandas, use to_csv(mode="a", header=False). Certifique-se de que a ordem das colunas corresponde ao arquivo existente, ou os dados irão parar nas colunas erradas. Se não tiver certeza sobre a ordem das colunas no arquivo existente, abra-o primeiro com csv.DictReader e leia os fieldnames a partir do atributo fieldnames antes de criar o writer para anexação.
import csv
new_records = [
{"order_id": "ord_f4c1", "total": 89.50, "status": "shipped"},
]
with open("orders.csv", "a", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["order_id", "total", "status"])
writer.writerows(new_records)Qual é a forma mais rápida de converter um arquivo JSON grande para CSV em Python?
Para arquivos abaixo de 500 MB, pd.read_json() seguido de to_csv() é a abordagem mais rápida de chamada única — o pandas usa código C otimizado internamente. Para arquivos acima de 500 MB, use ijson para transmitir registros JSON e escrevê-los no CSV com csv.DictWriter linha a linha. Isso mantém o uso de memória constante independentemente do tamanho do arquivo. Para arquivos NDJSON (um objeto JSON por linha), você não precisa de ijson — um simples loop for do Python sobre o identificador de arquivo processa cada linha de forma independente e mantém memória constante sem nenhuma biblioteca de terceiros.
# Rápido para arquivos que cabem na memória
import pandas as pd
df = pd.read_json("large_dataset.json")
df.to_csv("large_dataset.csv", index=False)
# Streaming para arquivos que não cabem na memória
import ijson, csv
with open("huge.json", "rb") as jf, open("huge.csv", "w", newline="") as cf:
records = ijson.items(jf, "item")
first = next(records)
writer = csv.DictWriter(cf, fieldnames=first.keys())
writer.writeheader()
writer.writerow(first)
for record in records:
writer.writerow(record)Posso escrever a saída CSV no stdout em vez de um arquivo em Python?
Sim. Passe sys.stdout como o objeto de arquivo para csv.writer() ou csv.DictWriter(). Isso é útil para redirecionar a saída em scripts shell ou para depuração rápida. Com pandas, chame to_csv(sys.stdout, index=False) ou to_csv(None) para obter uma string que você pode imprimir. Nenhum arquivo temporário necessário. Ao escrever no stdout no Windows, chame sys.stdout.reconfigure(newline="") primeiro para evitar o problema de duplo retorno de carro, já que o stdout é aberto em modo texto por padrão.
import csv
import sys
import json
data = json.loads('[{"host":"web-1","cpu":72.3},{"host":"web-2","cpu":45.1}]')
writer = csv.DictWriter(sys.stdout, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
# host,cpu
# web-1,72.3
# web-2,45.1Ferramentas relacionadas
Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.
Priya is a data scientist and machine learning engineer who has worked across the full Python data stack — from raw data ingestion and cleaning to model deployment and monitoring. She is passionate about reproducible research, Jupyter-based workflows, and the practical engineering side of ML. She writes about NumPy, Pandas, data serialisation, and the Python patterns that make data pipelines reliable at scale.