JSON es un árbol. CSV es un rectángulo. Convertir uno en otro implica decidir cómo aplanar el árbol, y cada conversor toma esas decisiones por ti, casi siempre sin decir cuáles tomó. Por eso el mismo archivo pasado por tres conversores en línea puede dar tres hojas de cálculo distintas.
Si conoces las tres decisiones, puedes elegir la herramienta, o los ajustes, que las toman como tú quieres.
La respuesta rápida
- Un arreglo plano de objetos (
[{"id":1,"name":"Ada"}, …]): sirve cualquier conversor. Usajq, Python, DuckDB, Excel o una app. - Objetos anidados (
"address": {"city": "Leeds"}): aplánalos en columnas con puntos,address.city.json_normalizede Python lo hace en una línea. - Arreglos dentro de cada registro (
"tags": ["a","b"]): decide entre una columna por elemento, una fila por elemento o una sola celda con la lista unida. No hay una respuesta correcta, solo la que encaja con lo que vas a hacer con los datos. - Los registros no están en la raíz: primero encuentra el arreglo
(normalmente
data,itemsoresults) y convierte ese.
Decisión 1: ¿qué arreglo se convierte en las filas?
Un CSV necesita una lista de cosas parecidas, una por fila. Casi ningún JSON real empieza con una. La respuesta de una API se ve así:
{
"status": "ok",
"page": 1,
"data": {
"items": [
{ "id": 1, "name": "Ada", "address": { "city": "Leeds" }, "tags": ["admin"] },
{ "id": 2, "name": "Lin", "address": { "city": "Hue" }, "tags": [] }
]
}
}
Las filas están en data.items. Un conversor apuntado a la raíz ve un solo
objeto y te da una sola fila con status, page y una columna llena de JSON
escapado. Es la queja más común de "el conversor no funciona", y es un problema
de ruta, no del conversor.
Decisión 2: ¿qué pasa con los objetos anidados?
address es un objeto dentro de cada registro. La solución habitual, y la
correcta para la mayoría de los casos, es aplanarlo en nombres de columna con
puntos:
| id | name | address.city |
|---|---|---|
| 1 | Ada | Leeds |
| 2 | Lin | Hue |
La alternativa, meter el objeto completo en una celda como cadena JSON, conserva todo, pero te deja una columna que no puedes ordenar ni filtrar.
Decisión 3: ¿qué pasa con los arreglos dentro de un registro?
tags es una lista, y una lista no cabe en una celda. Hay tres opciones:
| Enfoque | Resultado | Útil para |
|---|---|---|
| Columnas con índice | tags[0], tags[1], … | Listas cortas con un máximo conocido |
| Expandir a filas | Una fila por etiqueta, el resto de columnas repetidas | Contar o filtrar por etiqueta |
| Unir en una celda | admin;editor | Leer, no analizar |
Expandir cambia el número de filas: una persona con tres etiquetas pasa a ser tres filas. Eso es correcto para "cuántas personas tienen cada etiqueta" e incorrecto para "cuántas personas hay". Decide para qué pregunta es el CSV antes de elegir.
Las otras pérdidas
Incluso un aplanado perfecto pierde cosas, porque CSV no puede expresarlas:
- Tipos.
"42"y42se vuelven42.truese vuelve el textotrue. - Nulo o vacío.
null,""y una clave ausente se vuelven una celda vacía. - Columnas de registros incompletos. Si solo un registro de 10 000 tiene un
campo
coupon, un conversor que arma el encabezado con las claves del primer registro lo descartará. Los buenos conversores toman la unión de las claves de todos los registros.
Con jq
Para un arreglo plano en la raíz:
jq -r '(.[0] | keys_unsorted) as $k | $k, (.[] | [.[$k[]]]) | @csv' in.json > out.csv
Esto arma el encabezado con el primer registro, así que descarta cualquier
clave que ese registro no tenga, que es el problema de los registros
incompletos que vimos arriba. Para un arreglo más abajo, reemplaza el primer
. por la ruta, como .data.items. Con datos anidados jq también puede, pero
la expresión deja de ser legible; pasa a Python.
Con Python
pandas.json_normalize toma las tres decisiones en una sola llamada y usa la
unión de las claves:
import json, pandas as pd
data = json.load(open("in.json"))
df = pd.json_normalize(data["data"]["items"], sep=".")
df.to_csv("out.csv", index=False)
Los objetos anidados se vuelven address.city. Las listas quedan como listas
en una celda; para expandirlas a filas, agrega df = df.explode("tags") antes
de guardar.
Con DuckDB
Para archivos grandes, DuckDB lee JSON directamente y escribe CSV sin cargar todo en memoria:
duckdb -c "COPY (SELECT * FROM read_json_auto('in.json')) TO 'out.csv'"
Para un arreglo plano es la ruta más rápida que existe. Los objetos anidados
salen con la notación struct propia de DuckDB en una sola columna, así que
desanídalos en el SELECT o usa Python para datos anidados.
Con Excel
Datos → Obtener datos → Desde un archivo → Desde JSON abre Power Query. Haz clic en A tabla y luego en el ícono de expandir del encabezado de columna (dos flechas) para expandir registros y listas nivel por nivel. Es de apuntar y hacer clic, y ves cada decisión mientras la tomas. Después guarda como CSV. Ojo con las conversiones de tipo al salir: Excel cambia los datos del CSV a menos que le indiques lo contrario.
Conversores en línea
Funcionan, y con datos públicos están bien. Dos advertencias. La mayoría oculta las tres decisiones, así que compara el número de filas y columnas del resultado con lo que esperabas. Y el JSON de una API suele contener tokens, correos o registros de clientes, así que prefiere un conversor que corra en el navegador sin subir nada.
En iPhone y iPad
Smart Json convierte JSON a CSV en lo que llama modo registros: un arreglo de
objetos pasa a ser las filas, los valores anidados se aplanan en columnas con
puntos (address.city, tags[0]) y el encabezado es la unión de las claves de
todos los registros. Encuentra por ti el primer arreglo de objetos y te deja
elegir otro, o escribir un JSONPath como $.data.items. El separador es
configurable, y un documento sin arreglo de objetos lo dice en lugar de
producir un volcado inútil de dos columnas.

La vista previa es gratis, así que ves exactamente cómo quedará el CSV. Copiar o guardar el resultado es Pro.
Muestra una vista previa del JSON como CSV con los valores anidados aplanados en columnas con puntos, te deja elegir el arreglo de registros con JSONPath y dice con claridad lo que CSV no puede conservar.
Qué haceUna vez que lo tienes en CSV, abrirlo en el teléfono es un pequeño problema aparte; esa guía lo cubre.
Revisa el resultado
Uses la herramienta que uses, tres comprobaciones:
- Número de filas. ¿Coincide con la cantidad de registros del arreglo (o es mayor, si expandiste una lista a propósito)?
- Número de columnas. ¿Un campo que aparece solo en algunos registros llegó al encabezado?
- Un valor anidado. Elige un registro con un objeto anidado y confirma que su valor cayó en la columna con puntos correcta.