JSON a CSV en Python — Ejemplos con DictWriter y pandas
Usa el Conversor de JSON a CSV gratuito directamente en tu navegador — sin instalación.
Probar Conversor de JSON a CSV online →Casi todo pipeline de datos termina por llegar al mismo paso: una API devuelve JSON, pero el siguiente consumidor — una hoja de cálculo, un script de importación, un comando Redshift COPY — necesita CSV. Convertir JSON a CSV en Python parece trivial hasta que te topas con objetos anidados, claves inconsistentes o valores datetime que requieren tratamiento especial. Python te ofrece dos caminos sólidos: los módulos integrados json + csv para scripts sin dependencias externas, y pandas para aplanar estructuras anidadas y manejar datasets más grandes — o el convertidor de JSON a CSV en línea para conversiones rápidas y puntuales sin necesidad de código. Esta guía cubre ambos enfoques de principio a fin, con ejemplos ejecutables en Python 3.8+.
- ✓csv.DictWriter convierte una lista de dicts a CSV sin dependencias — usa json.load() para analizar y luego writeheader() + writerows().
- ✓Abre siempre los archivos CSV con newline="" en Windows para evitar filas en blanco entre los datos.
- ✓pd.json_normalize() aplana JSON anidado en un DataFrame plano antes de llamar a to_csv() — maneja automáticamente el anidamiento multinivel.
- ✓Pasa index=False a DataFrame.to_csv() — sin eso, pandas escribe una columna no deseada con el número de fila.
- ✓Para archivos mayores de 500 MB, usa ijson para el análisis JSON en streaming combinado con csv.DictWriter para un uso de memoria constante.
¿Qué es la conversión de JSON a CSV?
La conversión de JSON a CSV transforma un array de objetos JSON en un formato tabular donde cada objeto se convierte en una fila y cada clave en un encabezado de columna. JSON es jerárquico — los objetos pueden anidarse de forma arbitrariamente profunda. CSV es plano — cada valor se ubica en una cuadrícula de filas y columnas. La conversión funciona perfectamente cuando todos los objetos comparten el mismo conjunto de claves de nivel superior. Los objetos anidados, los arrays y las claves inconsistentes son donde las cosas se complican. Los datos en bruto permanecen idénticos; solo cambia la estructura.
[{"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 — Convierte JSON a CSV sin pandas
El módulo csv viene incluido en toda instalación de Python. Sin pip install, sin malabares con entornos virtuales. csv.DictWriter toma una lista de diccionarios y escribe cada uno como una fila CSV, mapeando las claves del dict a los encabezados de columna. El parámetro fieldnames controla tanto el orden de las columnas como qué claves se incluyen.
import json
import csv
# Datos JSON de ejemplo — un 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.95El argumento newline="" en open() no es opcional en Windows. Sin él, obtienes retornos de carro dobles — que aparecen como filas en blanco entre cada fila de datos en Excel. En macOS y Linux no tiene efecto, así que simplemente inclúyelo siempre.
El código anterior usa json.loads() para una cadena. Usa json.load() (sin la s final) cuando leas desde un handle de archivo. Esto confunde a la gente constantemente — uno lee una cadena, el otro lee un objeto de archivo.
import json
import csv
with open("server_metrics.json", encoding="utf-8") as jf:
metrics = json.load(jf) # json.load() para objetos de archivo
# Los fieldnames explícitos controlan el orden de las columnas
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)
# Solo aparecen las cinco columnas especificadas, exactamente en ese ordenEstablecer extrasaction="ignore" descarta silenciosamente cualquier clave de los dicts que no esté en tu lista de fieldnames. El valor predeterminado es "raise", que lanza un ValueError si algún dict tiene una clave inesperada. Elige el que se ajuste a tu tolerancia a las sorpresas.
csv.DictWriter vs csv.writer: DictWriter mapea automáticamente las claves del dict a las posiciones de columna. csv.writer escribe listas sin procesar como filas — tú te encargas del orden de columnas. DictWriter es casi siempre la elección correcta para JSON-a-CSV porque los registros JSON ya son diccionarios.El módulo csv de Python incluye tres dialectos con nombre: excel (delimitador coma, terminaciones de línea CRLF — el predeterminado), excel-tab (delimitador tabulador, terminaciones CRLF), y unix (terminaciones LF, cita todos los campos no numéricos). Pasa el nombre del dialecto como argumento dialect a csv.DictWriter. También puedes definir un dialecto personalizado con csv.register_dialect() cuando tu sistema de destino tenga reglas inusuales de comillas o delimitadores. Para la mayoría de los flujos de trabajo JSON-a-CSV, el dialecto excel es el correcto, pero cambia a unix al escribir archivos que serán procesados por herramientas POSIX como awk o sort.
Manejo de tipos no estándar: datetime, UUID y Decimal
El JSON de las APIs suele contener fechas como cadenas ISO, UUIDs como cadenas con guiones, y valores monetarios como flotantes. Cuando los parseas como objetos Python para procesarlos antes de escribir el CSV, necesitas convertirlos de vuelta a cadenas. El módulo csv llama a str() en cada valor, así que la mayoría de los tipos funcionan sin problemas. Pero los objetos datetime producen representaciones de cadena predeterminadas poco elegantes, y los valores Decimal necesitan formato explícito para evitar la notación científica.
import json
import csv
from datetime import datetime, timezone
from decimal import Decimal
from uuid import UUID
# Simulando respuesta de API parseada con 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:
"""Convierte tipos no-cadena a cadenas compatibles con 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 GmbHLa función prepare_row() es el enfoque correcto aquí. En lugar de intentar enseñarle a csv.DictWriter sobre tipos personalizados, normalizas cada registro a cadenas antes de escribir. Prefiero llamar a .isoformat() explícitamente sobre objetos datetime en lugar de depender de str() — el formato de salida es más predecible, y los parsers posteriores manejan ISO 8601 de forma fiable.
Decimal pasen sin formato, los números muy pequeños o muy grandes pueden representarse en notación científica (ej. 1.5E+7). Formatea siempre Decimal con una f-string explícita como f"{value:.2f}" al escribir datos financieros en CSV.Un patrón alternativo para pipelines con muchos tipos personalizados es extender json.JSONEncoder. Crea una subclase, sobreescribe el método default() para devolver un valor serializable a JSON por cada tipo personalizado, luego pasa la subclase como argumento cls a json.dumps(). Recodificar a través del encoder personalizado antes de escribir en CSV normaliza todos los tipos en un solo paso sin una llamada por fila a prepare_row(). El patrón prepare_row() mostrado arriba es más sencillo para scripts puntuales; el enfoque de subclase JSONEncoder escala mejor cuando el mismo modelo de dominio con tipos personalizados se comparte entre muchas etapas de pipeline o microservicios.
Referencia de parámetros de csv.DictWriter
La firma completa del constructor es csv.DictWriter(f, fieldnames, restval="", extrasaction="raise", dialect="excel", **fmtparams). La mayoría tienen valores predeterminados razonables. Los que realmente cambiarás son fieldnames, delimiter, y extrasaction.
pandas — Convierte JSON a CSV con DataFrames
Si ya trabajas en una base de código con mucho uso de pandas, o tu JSON tiene objetos anidados que necesitas aplanar, el enfoque con pandas requiere significativamente menos código que la versión de la biblioteca estándar. La contrapartida: pandas es una dependencia de ~30 MB. Para un script desechable, está bien. Para una imagen Docker que despliegas en producción, el enfoque con stdlib mantiene las cosas más ligeras.
import pandas as pd
# Lee el array JSON directamente en un DataFrame
df = pd.read_json("warehouse_inventory.json")
# Escribe en CSV — index=False evita los números de fila autogenerados
df.to_csv("warehouse_inventory.csv", index=False)
# Eso es todo. Dos líneas. pandas infiere los tipos de columna automáticamente.El flag index=False es de esas cosas que buscas cada vez. Sin él, pandas escribe una columna 0, 1, 2, ... como la primera columna de tu CSV. Nadie quiere eso.
Aplanar JSON anidado con json_normalize
Las respuestas reales de las APIs rara vez son planas. Los pedidos contienen direcciones de envío, los usuarios tienen preferencias anidadas, los eventos de telemetría tienen metadatos anidados. pd.json_normalize() recorre los diccionarios anidados y los aplana en columnas con nombres separados por puntos.
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 aplana los dicts anidados — sep controla el delimitador
df = pd.json_normalize(orders, sep="_")
df.to_csv("flat_orders.csv", index=False)
# Columnas resultantes:
# order_id, placed_at, customer_name, customer_email, customer_tier,
# shipping_method, shipping_address_city, shipping_address_state,
# shipping_address_zip, totalEl parámetro sep="_" controla cómo se unen los nombres de claves anidadas. El valor predeterminado es ".", que produce columnas como customer.name. Prefiero los guiones bajos porque los puntos en los nombres de columnas causan problemas con las importaciones SQL y algunas fórmulas de hojas de cálculo.
Para respuestas de API que envuelven el array de registros bajo una clave anidada, usa el parámetro record_path. Si la respuesta tiene el aspecto de {"data": {"orders": [...]}}, pasa record_path=["data", "orders"] para navegar a la lista correcta. El parámetro opcional meta te permite extraer campos del nivel superior junto a los registros anidados — útil cuando la respuesta incluye información de paginación de nivel superior (número de página, total) que quieres como columna en cada fila. Juntos, record_path y meta manejan la mayoría de las formas de respuesta de API anidadas del mundo real sin preprocesamiento personalizado.
Referencia de parámetros de DataFrame.to_csv()
DataFrame.to_csv() tiene más de 20 parámetros. Estos son los que importan para los flujos de trabajo de JSON a CSV.
import pandas as pd
df = pd.read_json("telemetry_events.json")
# Salida TSV con codificación explícita y manejo de valores faltantes
df.to_csv(
"telemetry_events.tsv",
sep="\t",
index=False,
encoding="utf-8",
na_rep="NULL",
columns=["event_id", "timestamp", "source", "severity", "message"],
)
# Escribir en stdout para canalizar en scripts de shell
print(df.to_csv(index=False))
# Devolver como cadena (sin escribir archivo)
csv_string = df.to_csv(index=False)
print(len(csv_string), "characters")Convertir JSON a CSV desde un archivo y una respuesta de API
Los dos escenarios más comunes del mundo real: leer JSON desde un archivo en disco y convertirlo, o obtener JSON de una API HTTP y guardar el resultado como CSV. En desarrollo puedes salirte con la tuya sin manejo de errores. En producción, esa elección se convierte en una alerta a las 2 de la mañana. Los archivos pueden no existir, las APIs pueden devolver códigos 4xx o 5xx en lugar de JSON, el cuerpo de la respuesta puede ser un objeto de error en lugar de un array, o el JSON puede estar truncado por un tiempo de espera de red. Los patrones a continuación manejan explícitamente todos estos casos, registran errores en stderr y devuelven un recuento de filas para que los llamadores puedan detectar salidas con cero filas y alertar en consecuencia.
Archivo en disco — Leer, convertir, guardar
import json
import csv
import sys
def json_file_to_csv(input_path: str, output_path: str) -> int:
"""Convierte un archivo JSON que contiene un array de objetos a CSV.
Devuelve el número de filas 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
# Recopila todas las claves únicas de todos los registros — maneja 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")Respuesta de API HTTP — Obtener y convertir
import json
import csv
import urllib.request
import urllib.error
def api_response_to_csv(url: str, output_path: str) -> int:
"""Obtiene JSON de un endpoint REST de API y lo escribe 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 de la biblioteca estándar para mantener el script sin dependencias. Si tienes requests instalado, reemplaza la sección de urllib con resp = requests.get(url, timeout=30); records = resp.json() — el resto del código de escritura CSV permanece idéntico.Conversión de JSON a CSV por línea de comandos
A veces solo necesitas un one-liner en la terminal. El flag -c de Python te permite ejecutar una conversión rápida sin crear un archivo de script. Para transformaciones más complejas, canaliza primero por jq para remodelar los datos, luego convierte.
# One-liner de Python: lee JSON de stdin, escribe CSV en 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) " # Guardar la salida en un archivo 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
# Guarda como json2csv.py y ejecuta: 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)
" "$@"# Instala csvkit: pip install csvkit
# jq aplana y selecciona campos, in2csv maneja el formato CSV
cat api_response.json | jq '[.[] | {id: .order_id, customer: .customer.name, total}]' | in2csv -f json > orders.csv
# Miller (mlr) es otra opción para JSON a CSV
mlr --json2csv cat orders.json > orders.csvMiller (mlr) es un binario independiente que trata JSON, CSV y TSV como formatos de primera clase sin necesidad de un entorno Python. El flag --json2csv convierte la entrada JSON a CSV en un solo paso, y puedes encadenar verbos de Miller para filtrar, ordenar o renombrar columnas en el mismo comando antes de escribir la salida. Instálalo con Homebrew en macOS (brew install miller) o con el gestor de paquetes de tu distribución Linux. Es especialmente útil en pipelines CI donde quieres una conversión rápida de JSON a CSV sin levantar un entorno Python.
Alternativa de alto rendimiento — pandas con pyarrow
Para datasets en el rango de decenas de millones de filas, pandas con el backend pyarrow lee y escribe significativamente más rápido que el predeterminado. El motor Arrow respaldado en C procesa datos en columnas de forma más eficiente que el módulo csv de Python fila por fila. La API permanece igual — solo estableces el parámetro engine.
pip install pyarrow
import pandas as pd
# Lee JSON con el motor pyarrow (análisis más rápido para archivos grandes)
df = pd.read_json("sensor_readings.json", engine="pyarrow")
# to_csv no tiene un parámetro engine, pero las operaciones sobre el DataFrame
# entre lectura y escritura se benefician del diseño columnar de pyarrow
df.to_csv("sensor_readings.csv", index=False)
# Para exportaciones verdaderamente grandes, considera escribir en Parquet en lugar de CSV
# — formato binario, 5-10x más pequeño, preserva los tipos
df.to_parquet("sensor_readings.parquet", engine="pyarrow")Si estás procesando más de unos pocos cientos de MB de JSON y el consumidor final acepta Parquet, omite CSV por completo. Parquet es más pequeño, preserva los tipos de columna, y tanto Redshift como BigQuery lo cargan de forma nativa. CSV es un formato con pérdida — cada valor se convierte en una cadena.
Salida en terminal con resaltado de sintaxis
La librería rich renderiza tablas con bordes, alineación y color en la terminal — útil para previsualizar una conversión durante el desarrollo sin tener que abrir el archivo de salida.
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="Vista previa de métricas del 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 una tabla con colores y bordes en la terminalcsv.DictWriter o DataFrame.to_csv(), y usa rich solo para previsualizar.Trabajar con archivos JSON grandes
json.load() lee el archivo completo en memoria. Para un archivo JSON de 200 MB, eso significa ~200 MB de texto en bruto más la sobrecarga de los objetos Python — fácilmente más de 500 MB de uso de heap. Para archivos mayores de 100 MB, transmite la entrada con ijson y escribe las filas CSV a medida que avanzas.
pip install ijson
Transmitir un array JSON a CSV con ijson
import ijson
import csv
def stream_json_to_csv(json_path: str, csv_path: str) -> int:
"""Convierte un array JSON grande a CSV sin cargarlo todo en memoria."""
with open(json_path, "rb") as jf, open(csv_path, "w", newline="", encoding="utf-8") as cf:
# ijson.items produce cada elemento del array de nivel superior de uno en uno
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 — Un objeto por línea
NDJSON (JSON con delimitador de nueva línea), también llamado JSON Lines o .jsonl, almacena un objeto JSON válido por línea sin un array envolvente. Este formato es común en pipelines de logs, flujos de eventos (Kafka, Kinesis) y exportaciones masivas de servicios como Elasticsearch y BigQuery. Dado que cada línea es un objeto JSON independiente, puedes procesar un archivo NDJSON con un bucle Python for sobre el handle del archivo — sin necesidad de la librería ijson. La memoria se mantiene constante independientemente del tamaño del archivo, lo que lo convierte en el enfoque de streaming más sencillo cuando tus datos de origen ya están en formato JSON Lines.
import json
import csv
def ndjson_to_csv(ndjson_path: str, csv_path: str) -> int:
"""Convierte un archivo JSON delimitado por nueva línea a CSV, una línea a la 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 # omitir líneas malformadas
return count
rows = ndjson_to_csv("access_log.ndjson", "access_log.csv")
print(f"Converted {rows} log entries to CSV")json.load() puede consumir 3–5 GB de RAM debido a la sobrecarga de objetos Python. Con ijson, la memoria se mantiene constante independientemente del tamaño del archivo. Si solo necesitas convertir rápidamente un archivo pequeño, pégalo en el convertidor de JSON a CSV en su lugar.Errores comunes
Problema: El módulo csv escribe terminaciones de línea . Sin newline='', el modo texto de Python añade otro en Windows, produciendo una salida con doble espaciado.
Solución: Pasa siempre newline='' al abrir un archivo para escritura CSV. No tiene efecto en macOS/Linux.
with open("output.csv", "w") as f:
writer = csv.DictWriter(f, fieldnames=columns)
writer.writeheader()
writer.writerows(data)
# Filas en blanco entre cada fila de datos en Windowswith open("output.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=columns)
writer.writeheader()
writer.writerows(data)
# Salida limpia en todas las plataformasProblema: Sin index=False, pandas antepone una columna de número de fila autoincremental (0, 1, 2, ...) que contamina el CSV con datos que nunca estuvieron en el JSON original.
Solución: Pasa index=False a to_csv(). Si realmente necesitas una columna de índice, nómbrala explícitamente con df.index.name = 'row_num'.
df = pd.read_json("events.json")
df.to_csv("events.csv")
# El CSV obtiene una columna extra sin nombre: ,event_id,timestamp,...
# La coma inicial rompe muchos parsers CSVdf = pd.read_json("events.json")
df.to_csv("events.csv", index=False)
# CSV limpio: event_id,timestamp,...Problema: Si los objetos JSON tienen claves diferentes (algunos registros tienen campos opcionales), usar las claves del primer registro como fieldnames descarta silenciosamente las columnas que solo aparecen en registros posteriores.
Solución: Recopila todas las claves únicas de todos los registros antes de crear el DictWriter.
records = json.load(f) writer = csv.DictWriter(out, fieldnames=records[0].keys()) # Pierde el campo "discount" que solo aparece en 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 clave de cada registro se incluye como columna
Problema: csv.DictWriter llama a str() sobre los dicts anidados, produciendo columnas con valores como "{'city': 'Portland'}"— repr de Python en bruto, no datos reales.
Solución: Aplana primero los objetos anidados usando pd.json_normalize() o una función de aplanado personalizada.
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
writer = csv.DictWriter(f, fieldnames=["id", "meta"])
writer.writerows(records)
# la columna meta contiene: {'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)
# Columnas: id, meta_source, meta_regioncsv.DictWriter vs pandas — Comparación rápida
Usa csv.DictWriter cuando necesites cero dependencias, tu JSON sea plano y el script se ejecute en un entorno restringido (contenedores CI, funciones Lambda, Python embebido). Usa pd.json_normalize() + to_csv() cuando el JSON esté anidado, necesites transformar o filtrar datos antes de exportar, o ya estés en un flujo de trabajo de pandas. Para archivos que no caben en memoria, combina ijson con csv.DictWriter para streaming con memoria constante.
Para conversiones rápidas sin código, el convertidor de JSON a CSV en ToolDeck lo gestiona sin ninguna configuración de Python.
Preguntas frecuentes
¿Cómo convierto JSON a CSV en Python sin pandas?
Usa los módulos integrados json y csv. Llama a json.load() para analizar el archivo JSON en una lista de dicts, extrae los fieldnames de las claves del primer dict, crea un csv.DictWriter, llama a writeheader() y luego a writerows(). Este enfoque tiene cero dependencias externas y funciona en cualquier entorno Python 3.x. También es más rápido que pandas para archivos pequeños, ya que no hay sobrecarga de asignación de DataFrame. Si tus objetos JSON tienen claves inconsistentes entre registros, recopila primero todas las claves únicas con dict.fromkeys(k for r in records for k in r) antes de pasarlas como fieldnames para evitar columnas faltantes.
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)¿Cómo manejo JSON anidado al convertir a CSV?
Los arrays JSON planos se mapean directamente a filas CSV, pero los objetos anidados necesitan ser aplanados primero. Con pandas, pd.json_normalize() lo maneja automáticamente — une las claves anidadas con un separador de punto (ej. "address.city"). Sin pandas, escribe una función recursiva que recorra el dict y concatene las claves con un delimitador. Para estructuras profundamente anidadas con múltiples niveles, json_normalize las maneja todas en un solo paso. El parámetro sep controla el carácter de unión entre segmentos de claves — el guion bajo suele ser más seguro que el punto predeterminado para importaciones SQL y compatibilidad con fórmulas de hojas de cálculo.
import pandas as pd
nested_data = [
{"id": "ord_91a3", "customer": {"name": "Ana García", "email": "a.garcia@ejemplo.com"}},
]
df = pd.json_normalize(nested_data, sep="_")
# Columnas: id, customer_name, customer_email
df.to_csv("flat_orders.csv", index=False)¿Por qué mi CSV tiene filas en blanco entre los datos en Windows?
El módulo csv escribe terminaciones de línea \r\n por defecto. En Windows, abrir el archivo en modo texto añade otro \r, produciendo \r\r\n — que se muestra como una fila en blanco. La solución es pasar siempre newline="" a open(). Esto le dice a Python que no traduzca los finales de línea, dejando que el módulo csv los gestione. Este patrón es obligatorio independientemente del sistema operativo — no tiene efecto en macOS y Linux, y es fundamental en Windows. La documentación de Python lo menciona explícitamente en la sección del módulo csv como la forma correcta de abrir archivos para escritura CSV.
# Incorrecto — filas en blanco en Windows
with open("output.csv", "w") as f:
writer = csv.writer(f)
# Correcto — newline="" evita el \r doble
with open("output.csv", "w", newline="") as f:
writer = csv.writer(f)¿Cómo añado registros JSON a un archivo CSV existente?
Abre el archivo en modo append ("a") y crea un DictWriter con los mismos fieldnames. Omite writeheader() ya que la fila de encabezado ya existe. Con pandas, usa to_csv(mode="a", header=False). Asegúrate de que el orden de columnas coincida con el archivo existente, o los datos irán a las columnas incorrectas. Si no estás seguro del orden de columnas en el archivo existente, ábrelo primero con csv.DictReader y lee los fieldnames de su atributo fieldnames antes de crear el writer para añadir.
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)¿Cuál es la forma más rápida de convertir un archivo JSON grande a CSV en Python?
Para archivos menores de 500 MB, pd.read_json() seguido de to_csv() es el enfoque más rápido de una sola llamada — pandas usa código C optimizado internamente. Para archivos mayores de 500 MB, usa ijson para transmitir los registros JSON y escribirlos en CSV con csv.DictWriter fila por fila. Esto mantiene el uso de memoria constante independientemente del tamaño del archivo. Para archivos NDJSON (un objeto JSON por línea), no necesitas ijson en absoluto — un bucle Python for sobre el handle del archivo procesa cada línea de forma independiente y logra memoria constante sin ninguna biblioteca de terceros.
# Rápido para archivos que caben en memoria
import pandas as pd
df = pd.read_json("large_dataset.json")
df.to_csv("large_dataset.csv", index=False)
# Streaming para archivos que no caben en memoria
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)¿Puedo escribir la salida CSV en stdout en lugar de un archivo en Python?
Sí. Pasa sys.stdout como objeto de archivo a csv.writer() o csv.DictWriter(). Esto es útil para canalizar la salida en scripts de shell o para depuración rápida. Con pandas, llama a to_csv(sys.stdout, index=False) o to_csv(None) para obtener una cadena que puedas imprimir. No se necesita ningún archivo temporal. Al escribir en stdout en Windows, llama primero a sys.stdout.reconfigure(newline="") para evitar el problema del doble retorno de carro, ya que stdout se abre en modo texto por defecto.
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.1Herramientas 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.