Convertir CSV a JSON en pandas: read_csv, to_json y orient
pandas lee casi cualquier CSV y escribe casi cualquier JSON en dos llamadas. La pega es el parámetro orient: si eliges mal, los consumidores aguas abajo reciben los datos correctos en una forma que no pueden usar.
read_csv a DataFrame a to_json es el pipeline
La conversión mínima viable son tres líneas:
import pandas as pd
df = pd.read_csv("input.csv")
df.to_json("output.json", orient="records", indent=2)
read_csv es permisivo: olfatea delimitadores, infiere dtypes, parsea fechas si le dices qué columnas. to_json es el inverso para JSON. El parámetro interesante es orient, que controla la forma del JSON, no los datos.
orient: records vs split vs index vs table
Cinco valores importan en la práctica.
orient="records" produce un array JSON de objetos, uno por fila. Es la lingua franca para APIs HTTP y para casi todo el código JS. Úsalo por defecto a menos que tengas razón para no hacerlo.
orient="index" produce un objeto indexado por el índice de fila, con cada valor siendo la fila como objeto. Útil cuando el índice es una clave significativa (un username, un SKU) y los consumidores quieren lookup O(1) sin escanear.
orient="split" produce {"columns": [...], "index": [...], "data": [[...]]}. Compacto, round-trip perfecto por pandas, horrible para consumidores no pandas.
orient="table" emite un JSON Table Schema con metadatos de tipo por columna. Útil si aguas abajo también es pandas o un consumidor estricto con validación de schema; verboso y rara vez lo que quieres en otros sitios.
orient="values" produce un array 2D sin nombres de columna. Payload mínimo, exige que el consumidor conozca el orden de columnas fuera de banda. Úsalo para matrices, nunca para datos tabulares.
force_ascii y date_format son las trampas
to_json por defecto pone force_ascii=True, que escapa cualquier carácter no ASCII a forma \u00e9. El resultado es JSON válido, pero más grande y menos legible. Pasa force_ascii=False en cualquier pipeline UTF-8 moderno:
df.to_json("output.json", orient="records", force_ascii=False, indent=2)
date_format="iso" también merece la pena explícitamente. El default es epoch, que emite enteros de milisegundos UNIX y confunde a todo consumidor JSON que espera strings. ISO 8601 hace round-trip por cualquier parser JSON y base de datos, incluido new Date(s) de JavaScript.
JSON anidado dentro y fuera: json_normalize y chunksize
Si la entrada es JSON con objetos anidados y quieres CSV plano, el camino inverso es pd.json_normalize:
records = [{"id": 1, "user": {"name": "Ada", "city": "Madrid"}}]
df = pd.json_normalize(records)
# columnas: id, user.name, user.city
df.to_csv("flat.csv", index=False)
Para archivos que no caben en memoria, read_csv acepta chunksize. Devuelve un iterador de DataFrames, cada uno con hasta N filas:
with open("big.json", "w") as f:
f.write("[")
first = True
for chunk in pd.read_csv("big.csv", chunksize=10_000):
for row in chunk.to_dict(orient="records"):
if not first: f.write(",")
json.dump(row, f, ensure_ascii=False)
first = False
f.write("]")
Emite un array JSON streaming sin cargar el archivo completo nunca. Es más feo que to_json pero escala a gigabytes.
Ejemplo completo
pythonimport json
import pandas as pd
# 1. CSV a JSON básico, orient records, UTF-8 preservado
df = pd.read_csv("input.csv", parse_dates=["created_at"])
df.to_json(
"output.json",
orient="records",
indent=2,
force_ascii=False,
date_format="iso",
)
# 2. orient index cuando el índice es una clave significativa
df_indexed = df.set_index("user_id")
df_indexed.to_json("by_user.json", orient="index", force_ascii=False)
# 3. Streaming para archivos que no caben en memoria
with open("big.json", "w", encoding="utf-8") as f:
f.write("[")
first = True
for chunk in pd.read_csv("big.csv", chunksize=10_000):
for row in chunk.to_dict(orient="records"):
if not first:
f.write(",")
json.dump(row, f, ensure_ascii=False, default=str)
first = False
f.write("]") ¿Solo necesitas el resultado?
Cuando tienes un CSV pequeño en la carpeta de descargas y solo quieres echarle un ojo en JSON, abrir un kernel de Jupyter es exagerado. El convertidor de CSV a JSON en navegador de aldeacode.com corre entero en tu máquina, elige el orient adecuado, preserva UTF-8 y te deja el resultado en una caja que copias y pegas.
Abrir Convertidor CSV ↔ JSON →Preguntas frecuentes
¿Por qué mis fechas salen como enteros largos en el JSON?
to_json usa date_format='epoch' por defecto en columnas datetime, lo que emite milisegundos UNIX. Pasa date_format='iso' para tener strings ISO 8601. Asegúrate de que read_csv parseó la columna con parse_dates primero; si no, la columna es un simple string y no pasa por el formateador de fechas.
¿Cómo conservo los acentos legibles en lugar de escapes \u?
Pasa force_ascii=False a to_json. La función está en True por defecto por compatibilidad con consumidores muy antiguos. En pipelines modernos pon False y escribe el archivo como UTF-8, lo cual pandas hace por defecto.
¿Qué orient uso para mandar datos a una API REST?
records, casi siempre. Produce un array JSON de objetos, la forma canónica que cualquier API REST y frontend JS espera. Usa index solo cuando la columna del índice sea un identificador significativo por el que los consumidores van a buscar.