JSON a CSV en Python — Ejemplos con DictWriter y pandas

·Backend Developer·Revisado porPriya Sharma·Publicado

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.

Before · json
After · json
[{"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.

Python 3.8+ — ejemplo mínimo de json a csv
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.95

El 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.

Python 3.8+ — leer archivo JSON, escribir archivo CSV
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 orden

Establecer 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.

Nota: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.

Python 3.8+ — preprocesar datetime y Decimal antes de escribir CSV
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 GmbH

La 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.

Advertencia:Si dejas que los valores 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.

Parámetro
Tipo
Predeterminado
Descripción
f
file object
(requerido)
Cualquier objeto con método write() — típicamente de open()
fieldnames
sequence
(requerido)
Lista de claves que define el orden de columnas en la salida CSV
restval
str
""
Valor escrito cuando a un dict le falta una clave de fieldnames
extrasaction
str
"raise"
"raise" lanza ValueError por claves extra; "ignore" las descarta silenciosamente
dialect
str / Dialect
"excel"
Reglas de formato predefinidas — "excel", "excel-tab" o "unix"
delimiter
str
","
Carácter único que separa los campos — usar "\t" para salida TSV
quotechar
str
"
Carácter usado para citar campos que contienen el delimitador
quoting
int
csv.QUOTE_MINIMAL
Controla cuándo se aplican comillas — MINIMAL, ALL, NONNUMERIC, NONE
lineterminator
str
"\r\n"
Cadena añadida después de cada fila — sobreescribir a "\n" para salida estilo Unix

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.

Python 3.8+ — pandas read_json y luego to_csv
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.

Python 3.8+ — aplanar JSON anidado usando json_normalize
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, total

El 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.

Parámetro
Tipo
Predeterminado
Descripción
path_or_buf
str / Path / None
None
Ruta de archivo o buffer — None devuelve el CSV como cadena
sep
str
","
Delimitador de campo — usar "\t" para TSV
index
bool
True
Escribir el índice de fila como primera columna — casi siempre se pone en False
columns
list
None
Subconjunto y reordenamiento de columnas en la salida
header
bool / list
True
Escribir nombres de columnas — poner en False al añadir a un archivo existente
encoding
str
"utf-8"
Codificación de salida — usar "utf-8-sig" para compatibilidad con Excel en Windows
na_rep
str
""
Representación en cadena de valores faltantes (NaN, None)
quoting
int
csv.QUOTE_MINIMAL
Controla cuándo se citan los campos
Python 3.8+ — to_csv con sobreescritura de parámetros comunes
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

Python 3.8+ — convertir archivo JSON a CSV con manejo de errores
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

Python 3.8+ — obtener JSON de API y guardar como CSV
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")
Nota:El ejemplo anterior usa 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.

bash — conversión de json a csv en una línea
# 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
bash — script CLI independiente con argparse
# 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)
" "$@"
bash — usar jq + csvkit para transformaciones complejas
# 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.csv

Miller (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.

bash — instalar pyarrow
pip install pyarrow
Python 3.8+ — pandas con pyarrow para escritura CSV más rápida
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.

bash — instalar rich
pip install rich
Python 3.8+ — previsualizar salida CSV en terminal con 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 terminal
Advertencia:Rich es solo para visualización en la terminal. No lo uses para generar archivos CSV — añade códigos de escape ANSI que corromperán la salida. Escribe en archivos con csv.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.

bash — instalar ijson
pip install ijson

Transmitir un array JSON a CSV con ijson

Python 3.8+ — transmitir array JSON grande a CSV con memoria constante
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.

Python 3.8+ — convertir NDJSON a CSV línea por línea
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")
Nota:Cambia a streaming cuando el archivo JSON supere los 100 MB. Un array JSON de 1 GB cargado con 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

Falta newline='' en open() — filas en blanco en Windows

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.

Before · Python
After · Python
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 Windows
with 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 plataformas
Olvidar index=False en pandas to_csv()

Problema: 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'.

Before · Python
After · Python
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 CSV
df = pd.read_json("events.json")
df.to_csv("events.csv", index=False)
# CSV limpio: event_id,timestamp,...
Usar records[0].keys() cuando los registros tienen claves inconsistentes

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.

Before · Python
After · Python
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
Escribir dicts anidados directamente en CSV sin aplanar

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.

Before · Python
After · Python
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_region

csv.DictWriter vs pandas — Comparación rápida

Método
JSON anidado
Tipos personalizados
Streaming
Dependencias
Requiere instalación
csv.DictWriter
✗ (aplanar manual)
✓ (fila por fila)
Ninguna
No (stdlib)
csv.writer
✓ (fila por fila)
Ninguna
No (stdlib)
pd.DataFrame.to_csv()
✗ (solo plano)
✓ (via dtypes)
pandas + numpy
pip install
pd.json_normalize() + to_csv()
✓ (via dtypes)
pandas + numpy
pip install
csv.writer + json_flatten
flatten_json
pip install
jq + csvkit (CLI)
✓ (via jq)
N/A
jq, csvkit
Instalación del sistema

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.

Python
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.

Python
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.

Python
# 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.

Python
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.

Python
# 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.

Python
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.1

Herramientas relacionadas

MS
Maria SantosBackend Developer

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.

PS
Priya SharmaRevisor técnico

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.