mygems

Cómo convertir JSON a CSV sin perder datos

JSON es un árbol y CSV un rectángulo, así que cada conversor toma decisiones. Cuáles son, cómo aplanar JSON anidado y qué herramientas lo hacen bien.

Por mygems ·

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. Usa jq, Python, DuckDB, Excel o una app.
  • Objetos anidados ("address": {"city": "Leeds"}): aplánalos en columnas con puntos, address.city. json_normalize de 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, items o results) 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:

idnameaddress.city
1AdaLeeds
2LinHue

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:

EnfoqueResultadoÚtil para
Columnas con índicetags[0], tags[1], …Listas cortas con un máximo conocido
Expandir a filasUna fila por etiqueta, el resto de columnas repetidasContar o filtrar por etiqueta
Unir en una celdaadmin;editorLeer, 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" y 42 se vuelven 42. true se vuelve el texto true.
  • 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 hoja Convertir con las pestañas YAML, CSV, TOML y XML, una nota sobre lo que el formato no puede conservar y una vista previa del documento convertido
Cada formato indica lo que no puede conservar antes de exportar

La vista previa es gratis, así que ves exactamente cómo quedará el CSV. Copiar o guardar el resultado es Pro.

JSON Editor: Smart JsoniPhone y iPad

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é hace

Una 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:

  1. Número de filas. ¿Coincide con la cantidad de registros del arreglo (o es mayor, si expandiste una lista a propósito)?
  2. Número de columnas. ¿Un campo que aparece solo en algunos registros llegó al encabezado?
  3. Un valor anidado. Elige un registro con un objeto anidado y confirma que su valor cayó en la columna con puntos correcta.

Preguntas

¿Cómo convierto JSON anidado a CSV?
Aplana los objetos anidados en nombres de columna con puntos, como address.city. En Python, pandas.json_normalize lo hace en una sola llamada; Power Query de Excel lo hace con el botón de expandir; algunas apps de JSON lo hacen automáticamente. Los arreglos dentro de los registros requieren otra decisión: columnas con índice, una fila por elemento o una celda unida.
¿Por qué mi conversor de JSON a CSV da una sola fila?
Los registros no están en la raíz del documento. Las respuestas de API normalmente los envuelven, por ejemplo en data.items. Apunta el conversor a ese arreglo, por su ruta, en lugar de al objeto raíz.
¿Puede Excel abrir un archivo JSON?
Sí, con Datos → Obtener datos → Desde un archivo → Desde JSON, que abre Power Query. Convierte el resultado en una tabla y usa los botones de expandir para aplanar los registros anidados; luego guarda como CSV.
¿Qué se pierde al convertir JSON a CSV?
Los tipos (números, booleanos y cadenas pasan a ser texto), la diferencia entre null, vacío y ausente, y el anidamiento en sí. Los campos que solo aparecen en algunos registros también pueden perderse si el conversor arma el encabezado con el primer registro.
¿Cómo convierto JSON a CSV en la línea de comandos?
Para un arreglo plano, jq con @csv o el COPY … TO 'out.csv' de DuckDB funcionan en una sola línea. Para JSON anidado, un script corto de Python con pandas.json_normalize es más confiable y más fácil de leer.

mygems — Desarrolla Smart Json, cuya exportación a CSV aplana JSON anidado, y Smart CSV Viewer, que lee el resultado.