FormatArc JSON to CSV: a la izquierda un array JSON con un objeto address anidado, a la derecha el CSV resultante con la cabecera name,email,role,address.cityFormatArc JSON to CSV: a la izquierda un array JSON con un objeto address anidado, a la derecha el CSV resultante con la cabecera name,email,role,address.city
Publicado: 2026-07-26

Convertir JSON a CSV — objetos anidados, arrays y nulls medidos en 4 conversores

TL;DR — elige un método en 10 segundos

  • Quieres la tabla ya: FormatArc JSON a CSV. Se ejecuta en tu navegador, no se sube nada y los objetos anidados se convierten en columnas con notación de punto.
  • Cada registro contiene un array de objetos (pedidos con líneas de detalle): decide primero si una fila es un pedido o una línea. Ve a Forma por forma.
  • Necesitas una fila por elemento del array: mlr --ijson --ocsv cat (Miller) o pandas.json_normalize(data, record_path=...).
  • Tus IDs tienen más de 15 dígitos: no uses un conversor escrito en JavaScript. Ve a enteros por encima de 2^53.
  • Vas a abrir el archivo en Excel: lee Abrir el CSV sin romperlo antes de hacer doble clic.
MétodoInstalaciónObjetos anidadosArrays dentro de un registroEn el navegador, sin subir datos
FormatArcningunacolumnas con notación de puntose conserva como texto JSON en una celda
json-2-csv (npm)npm i json-2-csvcolumnas con notación de puntose conserva como texto JSON en una celdano (Node)
Miller (mlr)brew install millercolumnas con notación de puntose expande a items.1.sku, items.2.skuno (CLI)
pandas json_normalizepip install pandascolumnas con notación de puntorepr de Python en una celda, o filas con record_pathno (Python)
jqbrew install jqescribes tú el mapeoescribes tú el mapeono (CLI)

Todo lo anterior está medido, no supuesto. Las entradas, el script de medición y las salidas en bruto están en el repositorio, en scripts/benchmarks/json-to-csv-guide/ (medido el 2026-07-26), y todas las salidas de abajo se citan tal cual de esa ejecución.

Convertir en 30 segundos

Abre JSON a CSV, pega un array de objetos y pulsa Ejecutar. Esta entrada:

[
  { "name": "Mika", "email": "mika@example.com", "role": "admin", "address": { "city": "Tokyo" } },
  { "name": "Noah", "email": "noah@example.com", "role": "viewer", "address": { "city": "Osaka" } }
]

produce este CSV:

name,email,role,address.city
Mika,mika@example.com,admin,Tokyo
Noah,noah@example.com,viewer,Osaka

FormatArc JSON a CSV convirtiendo un array JSON anidado en un CSV con la columna address.cityFormatArc JSON a CSV convirtiendo un array JSON anidado en un CSV con la columna address.city

La conversión ocurre dentro de la propia página: tu JSON nunca se envía a un servidor. Eso importa cuando lo que pegas es la respuesta de una API de producción con datos de clientes. En ¿Son seguros los conversores online? explicamos qué protege realmente esa diferencia.

Si la entrada se rechaza, el error llega con número de línea, por la misma ruta que usa JSON Formatter. Cómo corregir errores de parseo JSON cubre las causas habituales.

El mismo JSON, cuatro conversores, cuatro respuestas distintas

JSON es un árbol; CSV es un rectángulo. Cada conversor inventa reglas para aplastar uno dentro del otro, y esas reglas no coinciden entre herramientas. Pasamos 15 entradas por cuatro conversores y registramos la salida exacta de cada uno.

Entorno (según results.json): Node v26.3.1 en macOS arm64, FormatArc lib/tooling.ts (PapaParse 5.5.2), json-2-csv 5.5.11, Miller 6.19.0, pandas 3.0.5. Los cuatro se ejecutan con las opciones por defecto: eso es justamente lo interesante, lo que obtienes cuando no configuras nada.

Donde los cuatro coinciden

Los objetos anidados se aplanan con notación de punto. Con {"name":"Mika","address":{"city":"Tokyo","zip":"150-0001"}} los cuatro emiten:

name,address.city,address.zip
Mika,Tokyo,150-0001

Tres niveles se comportan igual (meta.created.by.name). Un objeto suelto en el nivel superior, sin array, produce un CSV de una fila en los cuatro. Y los valores con coma, comilla doble o salto de línea se entrecomillan igual:

who,quote,note
"Smith, John","She said ""hi""","line1
line2"

Eso coincide con las reglas de entrecomillado de la sección 2 de RFC 4180Se abre en una pestaña nueva: los campos con saltos de línea, comillas dobles o comas se encierran entre comillas dobles, y una comilla interna se escapa duplicándola. Una diferencia útil si comparas archivos: FormatArc termina los registros con CRLF (el valor por defecto de PapaParse, y lo que especifica RFC 4180), mientras que json-2-csv, Miller y pandas usan LF.

Los arrays se dividen en tres respuestas

Entrada:

[{"name":"Mika","tags":["admin","billing"]},{"name":"Noah","tags":["viewer"]}]
ConversorSalida
FormatArcname,tags / Mika,"[""admin"",""billing""]" / Noah,"[""viewer""]"
json-2-csvidéntica a FormatArc
Millername,tags.1,tags.2 / Mika,admin,billing / Noah,viewer,
pandasname,tags / Mika,"['admin', 'billing']" / Noah,['viewer']

Son tres productos distintos. FormatArc y json-2-csv guardan el array como texto JSON válido en una celda, así que puedes volver a parsearla más tarde. Miller ensancha la tabla, lo cual es cómodo hasta que un registro trae 40 etiquetas y el número de columnas se dispara. pandas escribe un repr de Python con comillas simples, que no es JSON válido y falla si algo aguas abajo intenta hacer JSON.parse de esa celda.

Los arrays de objetos se comportan igual: {"order":"A-1","items":[{"sku":"X1","qty":2},{"sku":"X2","qty":1}]} acaba en una celda de texto JSON en FormatArc y json-2-csv, y en items.1.sku, items.1.qty, items.2.sku, items.2.qty en Miller.

Claves faltantes: celda vacía, la palabra "undefined" o un error duro

Las respuestas reales de una API traen campos opcionales. Entrada:

[{"id":1,"name":"Mika"},{"id":2,"name":"Noah","nickname":"No"},{"id":3,"phone":"03-0000-0000"}]

FormatArc y pandas dejan celdas vacías:

id,name,nickname,phone
1,Mika,,
2,Noah,No,
3,,,03-0000-0000

json-2-csv escribe la cadena literal undefined en cada hueco:

id,name,nickname,phone
1,Mika,undefined,undefined
2,Noah,No,undefined
3,undefined,undefined,03-0000-0000

Miller rechaza el archivo entero: mlr: CSV schema change: first keys "id,name"; current keys "id,phone". Es una decisión razonable para una herramienta de streaming, pero significa que un solo campo opcional puede detener un pipeline que ayer funcionaba.

FormatArc toma la unión de todas las claves de todos los registros, en orden de primera aparición, así que el número de columnas queda fijado antes de escribir la primera fila. Ninguna fila puede desplazarse.

null: una celda vacía o las cuatro letras n-u-l-l

[{"id":1,"deleted_at":null},{"id":2,"deleted_at":"2026-07-01"}]

FormatArc y pandas escriben una celda vacía. json-2-csv y Miller escriben el texto null. Si luego cargas el CSV en una base de datos, un grupo te da un NULL real y el otro una cadena de cuatro caracteres que lo parece. Es la causa más frecuente de "por qué mi WHERE no encuentra nada" después de una conversión.

Un objeto vacío produce cuatro tablas distintas

Con [{"id":1,"meta":{}},{"id":2,"meta":{"source":"api"}}]:

ConversorSalida
FormatArcid,meta,meta.source — el {} vacío deja una columna meta que queda en blanco en todas las filas
json-2-csvmisma cabecera, pero escribe el literal {} y duplica el valor en {"source":"api"}
Millererror: CSV schema change: first keys "id,meta"; current keys "id,meta.source"
pandaselimina la columna meta por completo: id,meta.source

El comportamiento de FormatArc es honesto pero no bonito: te queda una columna vacía de más. Si ves una, hay un objeto vacío en algún punto del payload.

Un punto literal en el nombre de una clave sobrescribe una columna

Este caso cuesta datos, y tres de los cuatro conversores los pierden. Entrada:

[{"a.b":1,"a":{"b":2}}]

El registro tiene dos valores distintos: la clave que se llama literalmente a.b con el valor 1, y la ruta anidada a.b con el valor 2. FormatArc, Miller y pandas emiten:

a.b
2

El 1 ha desaparecido. Solo json-2-csv los distingue, escapando la clave literal:

a\.b,a.b
1,2

Si tu JSON usa nombres de clave con puntos (eventos de analítica tipo page.view.count, documentos estilo MongoDB, etiquetas al estilo Prometheus), comprueba las colisiones antes de convertir o usa json-2-csv.

Los enteros por encima de 2^53 se redondean en los conversores JavaScript

[{"id":9007199254740993,"order_no":12345678901234567890}]
ConversorSalida
FormatArc9007199254740992,12345678901234567000
json-2-csv9007199254740992,12345678901234567000
Miller9007199254740993,12345678901234567890
pandas9007199254740993,12345678901234567890

No es un problema del CSV ni un fallo de las dos herramientas JavaScript: JSON.parse convierte todos los números en dobles, y los dobles no pueden representar todos los enteros por encima de 2^53. El valor ya llega mal al escritor de CSV. Los IDs de Snowflake, los de X (Twitter), algunas referencias de pago y las claves de base de datos de 64 bits caen en ese rango. Si tus IDs son largos, entrecomíllalos como cadenas en el JSON o convierte con Miller, pandas o jq, que conservan los dígitos.

Entrada rota: un error explícito o un archivo vacío en silencio

EntradaFormatArcjson-2-csvMillerpandas
["a","b","c"]error: "cada elemento del array debe ser un objeto"tres líneas en blanco, sin errorexcepciónexcepción
[]error: "el array JSON está vacío"una línea en blanco, sin errorsalida vacía, sin errorsalida vacía, sin error

Una salida vacía con código de salida 0 es el peor de los tres resultados, porque un cron sobrescribirá tan tranquilo el archivo correcto de ayer con nada.

Qué hace exactamente el conversor de FormatArc

Estas reglas son la implementación, no un resumen de ella. Vienen de convertJsonToCsv en lib/tooling.ts y coinciden con las mediciones de arriba.

  1. El nivel superior debe ser un array de objetos o un objeto único. Un objeto único produce un CSV de una fila. Un array de primitivos, un array vacío y una cadena o número sueltos se rechazan con un mensaje.
  2. Los objetos anidados se aplanan recursivamente en columnas con notación de punto: address.city, meta.created.by.name. No hay límite de profundidad.
  3. Los arrays no se expanden. Se serializan con JSON.stringify y se guardan como texto JSON en una celda, de modo que ["admin","billing"] sigue siendo parseable.
  4. Las columnas son la unión de todas las claves de todos los registros, en orden de primera aparición. Una clave que solo aparece en el último registro también recibe columna, y las filas anteriores quedan vacías en ella. Las filas no pueden desplazarse.
  5. null y undefined producen celdas vacías. Un objeto vacío {} también produce una celda vacía y conserva su propia columna.
  6. El entrecomillado sigue a unparse de PapaParse: los campos con comas, comillas o saltos de línea se entrecomillan, las comillas internas se duplican y los registros terminan en CRLF.
  7. El JSON inválido se reporta con número de línea, por la misma ruta de error que JSON Formatter.

Nada sale de la pestaña. No hay paso de subida ni archivo temporal en un servidor: la conversión es una llamada a función dentro de la página que ya tenías cargada.

Forma por forma: qué hacer con cada tipo de JSON

Un array plano de objetos

No hay nada que decidir. Pega y ejecuta.

Registros con objetos anidados

La notación de punto es la respuesta por defecto en todas partes, y es reversible a mano: address.city te dice exactamente de dónde salió el valor. Comprueba dos cosas antes: nombres de clave que ya contienen un punto (ver arriba) y si quien consume el CSV tolera puntos en las cabeceras. Algunos cargadores SQL necesitan la cabecera entrecomillada; Google Sheets lo acepta sin problema.

Registros con un array de valores simples

Elige una opción:

  • Conservarlo como texto JSON en una celda (por defecto en FormatArc). Ideal cuando el CSV es un archivo intermedio que otro script volverá a leer.
  • Expandirlo a tags.1, tags.2 con Miller. Ideal cuando la longitud máxima es pequeña y fija (un par de coordenadas, un triplete RGB).
  • Unir con un separador antes de convertir, si lo va a leer una persona: jq '.[] |= (.tags |= join(";"))' data.json y luego convertir. Usa ; en lugar de , para que la celda no necesite comillas.

Registros con un array de objetos (el caso difícil)

Decide qué representa una fila del CSV antes de tocar ningún conversor.

  • Una fila por padre (un pedido por fila): deja el array como texto JSON en una celda. Es lo que hace FormatArc por defecto. Las líneas de detalle se conservan intactas y un script puede parsear la celda después.
  • Una fila por hijo (una línea de detalle por fila): eso es otra tabla, no otro formato. Usa pandas.json_normalize(data, record_path="items", meta=["order"]), que repite los campos del padre en cada fila hija, o reestructura antes con jq.
  • Dos archivos: convierte los campos del padre a orders.csv y extrae los hijos con jq '[.[] | .order as $o | .items[] | {order:$o} + .]' data.json para convertirlos a items.csv. Es la respuesta normalizada, y la que conviene si el CSV va a una base de datos.

Lo que conviene evitar con arrays de longitud variable es ensanchar la fila padre en items.1.sku, items.2.sku, … (el comportamiento por defecto de Miller): el número de columnas lo decide el registro que casualmente tenga más hijos, así que el esquema cambia cada vez que cambian los datos.

Registros con claves inconsistentes

FormatArc y pandas lo resuelven sin configuración. Miller necesita unsparsify, que rellena los huecos en lugar de abortar:

mlr --ijson --ocsv unsparsify data.json

También puedes unificar los registros antes con jq '[.[] | {id, name, nickname, phone}]', que obliga a que todos lleven todas las claves.

Abrir el CSV en Excel o Sheets sin romperlo

Lo que corrompe tus datos casi nunca es el conversor: es la hoja de cálculo.

  • Codificación. El CSV no tiene campo de codificación; RFC 4180Se abre en una pestaña nueva solo señala que los conjuntos de caracteres distintos de US-ASCII se usan mediante el parámetro MIME charset, que un archivo local no lleva. FormatArc descarga UTF-8 sin marca de orden de bytes (BOM). Excel en Windows puede entonces leer el texto no ASCII como Windows-1252, así que impórtalo desde Datos, Desde texto/CSV, eligiendo UTF-8, en lugar de hacer doble clic. Google Sheets detecta UTF-8 correctamente.
  • Números de más de 15 dígitos. La propia especificación de Excel indica una precisión numérica de 15 dígitosSe abre en una pestaña nueva; todo lo que exceda se sustituye por ceros. Junto con el redondeo de 2^53 de arriba, un ID largo puede dañarse dos veces. Importa esas columnas como Texto.
  • Ceros iniciales. 007 y 03-0000-0000 se convierten en 7 y en una fecha salvo que la columna se importe como Texto.
  • Cadenas con aspecto de fecha. Valores como 2026-07-01 y, peor aún, números de versión tipo 1-2 o nombres de genes, se convierten automáticamente. Impórtalos como Texto o corrige el tipo de columna en el diálogo de importación.
  • Celdas que empiezan por =, +, - o @. Ningún conversor de nuestra matriz las escapa: los cuatro escribieron =1+1 sin tocarlo. Quien decide si se evalúa es la hoja de cálculo, y en eso se basa la inyección CSVSe abre en una pestaña nueva. Si el CSV contiene texto introducido por usuarios y lo va a abrir otra persona, antepón una comilla simple a esas celdas o importa la columna como Texto.

Cuándo el navegador no es el sitio adecuado

Convertir en el navegador es lo más rápido para una respuesta de API que acabas de pegar desde curl. Es la herramienta equivocada para una exportación de 2 GB o para un paso dentro de un job nocturno.

# Miller: aplana objetos, expande arrays en columnas indexadas, procesa en streaming
mlr --ijson --ocsv cat data.json > out.csv

# jq: escribes el mapeo explícitamente, así que no se adivina nada
jq -r '(.[0] | keys_unsorted), (.[] | [.[]]) | @csv' data.json > out.csv

# pandas: aplanado con notación de punto y una fila por elemento del array con record_path
python3 -c "import pandas,json; print(pandas.json_normalize(json.load(open('data.json'))).to_csv(index=False))" > out.csv

Los tres no son intercambiables. json_normalize expande diccionarios en columnas con puntos, pero necesita record_path para producir una fila por elemento de un array. El @csv de jq exige que los datos ya estén en arrays de valores simples y falla ante un objeto anidado en lugar de aplanarlo. Miller aplana objetos y arrays por defecto con índices que empiezan en 1, y se detiene con un error de esquema ante registros heterogéneos, como medimos arriba. Los detalles están en la documentación de flatten de MillerSe abre en una pestaña nueva, el manual de jqSe abre en una pestaña nueva y la referencia de pandas.json_normalizeSe abre en una pestaña nueva.

Para la dirección contraria y sus propias trampas (inferencia de tipos, codificaciones, salida NDJSON), consulta Convertir CSV a JSON.

Volver atrás no es una conversión sin pérdidas

Una ida y vuelta no devuelve el documento original. Pasa el CSV del primer ejemplo por CSV a JSON:

[
  {
    "name": "Mika",
    "email": "mika@example.com",
    "role": "admin",
    "address.city": "Tokyo"
  }
]

address.city vuelve como una clave plana con un punto en el nombre, no como un objeto address anidado. Nada vuelve a anidar la notación de punto automáticamente, porque address.city es un nombre de clave perfectamente legal por sí mismo y el conversor no puede saber cuál de los dos querías: exactamente la colisión medida en el caso C13.

Trata la conversión de JSON a CSV como una exportación de un solo sentido para hojas de cálculo y análisis, y conserva el JSON como fuente de verdad. Si necesitas recuperar la forma anidada, hazlo de forma deliberada, por ejemplo con jq 'map(reduce (to_entries[]) as {$key,$value} ({}; setpath($key | split("."); $value)))', y revisa el resultado.

Preguntas frecuentes

¿Por qué mi CSV tiene una sola columna con JSON dentro?

Porque el nivel superior era un objeto único con un array grande dentro, tipo {"data":[...]}. Extrae primero el array (pega solo el valor de data, o ejecuta jq '.data' response.json) y convierte después. FormatArc aplana el envoltorio en columnas como data en lugar de tratar el array como las filas.

¿Puedo obtener una fila por elemento de un array anidado?

Con el conversor del navegador no, y es deliberado: el número de filas dependería de los datos y los campos del padre se duplicarían en silencio. Usa pandas.json_normalize(data, record_path="items", meta=[...]) o reestructura antes con jq, como se describe arriba.

¿Por qué una columna contiene [{"sku":"X1"...}] en vez de valores?

Es un array de objetos conservado como texto JSON en una sola celda. Es intencionado y reversible. Las tres formas de cambiarlo están en el caso difícil.

¿El conversor cambia mis números?

Solo como lo hace el propio JavaScript: los enteros por encima de 2^53 se redondean al parsear, como medimos en el caso C14. Las cadenas no se tocan. Si tus IDs tienen más de 15 dígitos, entrecomíllalos en el JSON o usa una herramienta que no sea JavaScript.

¿Se suben mis datos a algún sitio?

No. La conversión se ejecuta en la página, así que el JSON nunca sale de la pestaña: no existe ninguna petición que lo transporte. ¿Son seguros los conversores online? explica por qué importa esa distinción y cómo verificarlo tú mismo en el panel de red.

¿Y si mi JSON es inválido?

Recibes un mensaje con número de línea en lugar de un CSV roto. Cómo corregir errores de parseo JSON cubre comas finales, comillas simples y los demás sospechosos habituales, y consejos de formateo JSON explica cómo mantenerlo legible después.

¿Qué formato debo conservar como original?

El que lleva la estructura. El CSV es un rectángulo sin tipos: el anidamiento, los arrays y la diferencia entre null y "" se pierden. Conserva el JSON y trata el CSV como una vista de él. ¿Qué es CSV? detalla los límites del formato.