De JSON a CSV cuando la estructura está anidada: por qué no hay una respuesta correcta
Publicado el 17/7/2026 · 16 min de lectura · Herramientas para desarrolladores
Daniel Okonkwo — Desarrollador front-end y redactor de Tecnología en OneKitly
Rendimiento web · Formatos de archivo
Verificado con 4 fuentes
Aplanar JSON anidado a CSV no tiene una respuesta única, y dos conversores de este sitio lo demuestran discrepando sobre la misma entrada. Toma dos pedidos, cada uno con un objeto cliente, un array tags de cadenas y un array lines de objetos. El conversor CSV / JSON / YAML produce cinco columnas —id, customer, tags, lines, note— y reescribe cada valor anidado como texto JSON dentro de una sola celda, con las comillas internas dobladas. El conversor de JSON a CSV, puesto en aplanar, produce diez: id, customer.name, customer.city, tags.0, tags.1, lines.0.sku, lines.0.qty, lines.1.sku, lines.1.qty, note. Mismos datos, mismas dos filas, el doble de columnas, y ambos se sostienen. Serializar conserva la forma del registro y sobrevive a un viaje de ida y vuelta entre máquinas; aplanar hace que cada hoja sea ordenable y filtrable en una hoja de cálculo, a cambio de un reparto de columnas dictado por el array más largo del archivo: un pedido con tres líneas da a todos los pedidos nueve columnas de líneas, casi siempre vacías. Otras tres decisiones no tienen un valor por defecto natural. Un array de escalares se convierte en una columna por elemento, nunca en una cadena unida. Un array de objetos ensancha la tabla; ninguno de los dos conversores lo estalla en filas adicionales, que es lo que espera quien viene de las bases de datos. Y registros con juegos de claves distintos producen la unión de las columnas con celdas vacías para los huecos: null, la cadena vacía y una clave ausente quedan indistinguibles en cuanto se escriben. Un comportamiento conviene saberlo antes de fiarse: el modo aplanar pierde un valor cuando una clave literal a.b se cruza con un a.b anidado, y solo sobrevive el valor anidado.
Los mismos dos pedidos salen con cinco columnas de un conversor y diez de otro, y ninguno se equivoca. Rutas con puntos, arrays de escalares, arrays de objetos y registros con claves distintas: cuatro decisiones, tomadas por ti y casi siempre en silencio.
Los mismos dos pedidos, dos veces
Esta es la entrada, y no cambiará en todo el artículo. Dos pedidos. El primero tiene id 1, un objeto customer con name Emma y city Paris, un array tags de dos cadenas, y un array lines de dos objetos, cada uno con un sku y una qty. El segundo tiene id 2, un objeto customer con Liam y Berlin, un array tags de una cadena, un array lines de un objeto, y una clave extra que el primer registro no tiene: note, con valor urgent. Nada exótico: es la forma de cualquier pedido, factura o carga de evento que haya salido de una API.
Pásala por el conversor CSV / JSON / YAML y obtienes cinco columnas: id, customer, tags, lines, note. La celda customer de la primera fila contiene los caracteres {""name"":""Emma"",""city"":""Paris""} —el objeto reserializado a JSON y luego entrecomillado como campo CSV, lo que dobla todas las comillas de dentro—. La celda lines lleva el array entero igual. Abre eso en una hoja de cálculo y tienes dos filas, cinco columnas y tres celdas que no puedes ordenar, filtrar ni sumar.
Pasa exactamente el mismo JSON por el conversor de JSON a CSV con los valores anidados puestos en aplanar, y obtienes diez columnas: id, customer.name, customer.city, tags.0, tags.1, lines.0.sku, lines.0.qty, lines.1.sku, lines.1.qty, note. El segundo pedido tiene una etiqueta y una línea, así que tags.1, lines.1.sku y lines.1.qty quedan vacías en esa fila. Cada valor es ya un escalar en su propia columna. El número de filas no cambió —siguen siendo dos— y el archivo lo modela ahora el registro más grande, no el esquema.
Ninguna de las dos salidas es un error. Serializar es lo correcto cuando el CSV es un formato de transporte y algo va a analizar esas celdas más tarde: la forma del registro se conserva exacta, y un viaje de ida y vuelta entre máquinas devuelve lo que entró. Aplanar es lo correcto cuando una persona va a abrir el archivo: cada hoja es ordenable, filtrable y sumable. Lo que sí es un error es hacer una u otra sin saber cuál hiciste, y descubrir tres semanas después que el analista lleva contando filas en un archivo cuyo recuento de filas responde a una pregunta distinta de la suya.
Rutas con puntos, índices entre corchetes, y la clave que ya contiene un punto
Una vez decides aplanar, hay que nombrar las hojas. Dos grafías están muy extendidas. Las rutas con puntos escriben un índice de array como cualquier otra clave: tags.0, lines.1.sku. Los índices entre corchetes distinguen los dos tipos de paso: tags[0], lines[1].sku. El aplanador de JSON dedicado de este sitio ofrece ambas, más una elección de separador —punto, guion bajo o barra—, porque un guion bajo sobrevive al paso por sistemas que tratan el punto como operador de ruta, y una barra recoge la sintaxis de puntero que ya se conoce por JSON Pointer. El modo aplanar del conversor de JSON a CSV usa siempre el punto para ambos casos, la más compacta de las dos grafías y la que menos probablemente estropeen las hojas de cálculo.
Hay un fallo real escondido en la grafía con puntos, y conviene decirlo claro porque cuesta datos. Una ruta con puntos es ambigua: la columna customer.name puede significar la clave name dentro del objeto customer, o una clave de primer nivel cuyo nombre literal es customer.name. JSON permite las dos, en el mismo objeto. Dale al modo aplanar un registro que contenga una clave literal a.b con valor 1 junto a un objeto anidado a cuya clave b vale 2, y la salida tiene una sola columna, a.b, con el valor 2. El primer valor ha desaparecido, sin aviso y sin segunda columna. Es raro, pero no es hipotético: las claves con puntos aparecen en campos de registro, nombres de eventos de analítica y en cualquier cosa derivada de un identificador con espacio de nombres.
La defensa es la elección del separador. Aplana con un guion bajo o una barra en lugar del punto y la colisión exige una clave que contenga literalmente ese carácter, algo mucho menos probable. Si no puedes elegir el separador —y en el modo aplanar del conversor de CSV no puedes—, revisa si tus claves llevan puntos antes de aplanar, no después.
Los arrays: una columna por elemento, una sola celda, o una fila por elemento
Un array de escalares tiene tres respuestas sensatas. Serializarlo: la celda tags pasa a contener los caracteres ["vip","eu"]. Dar una columna a cada elemento: tags.0 y tags.1. O unir los elementos con un separador que no aparezca en ellos, de modo que la celda tags diga vip|eu y una fórmula de hoja de cálculo pueda volver a partirla. Los conversores de aquí hacen las dos primeras y ninguno hace la tercera: si quieres una cadena unida, tienes que producirla antes de convertir. La forma unida es la más legible y la única cuyo número de columnas no cambia cuando cambian los datos, y por eso tantas exportaciones la usan pese a ser la peor definida.
Un array de objetos es donde las herramientas y las bases de datos se separan. Con lines de dos entradas, el modo aplanar ensancha la tabla: lines.0.sku, lines.0.qty, lines.1.sku, lines.1.qty. Quien viene de las bases esperaría lo contrario: una fila de salida por línea de pedido, con los campos del pedido repetidos en el bloque, que es lo que produce un join y lo que quiere una tabla dinámica. Ninguno de los conversores lo hace, y la diferencia no es cosmética. Ensanchar mantiene una fila por pedido: un recuento de filas es un recuento de pedidos. Estallar da una fila por línea: un recuento de filas es un recuento de líneas y los campos del pedido quedan duplicados. Los dos se usan en la práctica; solo uno responde a la pregunta cuántos pedidos enviamos.
Conviene anticipar una consecuencia del ensanchado: el reparto de columnas lo fija el array más grande de todo el archivo, y cambia cuando cambian los datos. Dos registros cuyos arrays de etiquetas tienen uno y tres elementos producen las columnas t.0, t.1 y t.2, con dos celdas vacías en el registro corto. Exporta mañana la misma consulta con un registro de cuatro etiquetas y el archivo gana una columna, en silencio. Todo lo que aguas abajo lea columnas por posición y no por nombre se rompe ese día, y la exportación que lo rompió es idéntica a la anterior.
Registros que no se ponen de acuerdo sobre sus claves
JSON no tiene esquema, así que un array de objetos no es una tabla hasta que tú la haces. Los dos conversores toman la unión de todas las claves que ven y dejan un hueco donde un registro no la tiene. Tres registros con {id, a}, {id, b} y {id, a, c} dan cuatro columnas —id, a, b, c— con celdas vacías donde cada uno calla. Es la única respuesta que no pierde nada, y por eso un CSV exportado de una base documental suele ser mucho más ancho que cualquiera de sus documentos.
El orden de las columnas ni está ordenado ni es estable entre exportaciones. Los dos conversores usan el orden de primera aparición: las claves salen en el orden en que las presenta el primer registro que las contiene. Dos registros {b, a} y {a, b} producen las columnas b y luego a, porque el primero se leyó primero. Cambia el orden de tu consulta y el orden de columnas cambia con él, aunque los datos sean idénticos. Si algo aguas abajo depende del orden de columnas, ordena tú las claves antes de exportar.
Algo se pierde sea cual sea la estrategia, y la culpa es del CSV, no del conversor. Una clave con valor null, una clave con la cadena vacía y una clave simplemente ausente pasan a ser la misma celda vacía. Vuelve a leer ese CSV y todas regresan como cadena vacía. Si la distinción importa —y en una actualización parcial o en una columna anulable siempre importa—, el CSV no es el formato adecuado para ese campo, y ninguna opción de aplanado lo salvará.
Qué detecta el lado CSV y qué no
Empieza por el sentido que avergonzaba a este conversor. Dale al conversor CSV / JSON / YAML el JSON [1,2,3] y devuelve value / 1 / 2 / 3: una única columna sintetizada, porque un número no tiene clave propia que sirva de nombre de columna y value es lo único honesto que se le puede llamar. Un escalar de primer nivel, 42, da value / 42. Un array mixto, [1,{"a":2}], da la cabecera value,a y dos filas —1 seguido de una celda vacía, y luego una celda vacía seguida de 2—: el escalar en la columna inventada y el objeto en la suya. Es la misma respuesta que da el conversor hermano JSON a CSV, así que las dos herramientas del sitio ya coinciden en el caso que más a menudo llega de una API que devuelve una simple lista de identificadores.
En lectura, el conversor ahora olfatea su delimitador en vez de dar por hecho la coma: cuenta comas contra puntos y comas en el primer registro, ignorando lo que va entre comillas, así que name;city sobre Emma;Paris llega como {"name":"Emma","city":"Paris"}, lo cual importa porque Excel escribe CSV con punto y coma por defecto en cinco de los seis mercados de este sitio. Los nombres de columna repetidos se renombran en vez de perderse: name,name,name sobre a,b,c devuelve name, name_2 y name_3. Una fila que se pasa de la cabecera conserva la celda extra bajo un nombre inventado: a,b sobre 1,2,3 devuelve a, b y column3. Lo que sigue sin hacer es puntuar una tabulación o una barra vertical. La herramienta específica de CSV a JSON pesa cuatro candidatos y te deja forzar uno; este conversor pesa dos y no tiene ningún control de delimitador en su interfaz, así que un archivo separado por tabulaciones sigue llegando como una única columna cuya clave es toda la línea de cabecera. Y entre lo que entre, el CSV que escribe de vuelta va separado por comas.
| Valor anidado | Serializado (conversor CSV / JSON / YAML) | Aplanado (JSON a CSV, modo aplanar) | La decisión que se toma por ti |
|---|---|---|---|
| Un objeto: customer = {name, city} | Una columna, customer, con el texto JSON | Dos columnas, customer.name y customer.city | Ida y vuelta entre máquinas, u ordenación humana: no las dos |
| Un array de cadenas: tags = [vip, eu] | Una columna con ["vip","eu"] | Dos columnas, tags.0 y tags.1 | Ninguno los une en vip|eu; si lo quieres, constrúyelo antes de convertir |
| Un array de objetos: lines = dos líneas | Una columna con todo el array como texto JSON | Cuatro columnas: lines.0.sku, lines.0.qty, lines.1.sku, lines.1.qty | Ninguno lo estalla en una fila por línea: el recuento de filas sigue siendo de pedidos |
| Una clave que solo tiene el segundo registro: note | Una columna note, vacía en la primera fila | Lo mismo: una columna note, vacía en la primera fila | Unión de columnas con huecos: después, null, vacío y ausente son la misma celda |
| Una clave literal a.b junto a un a anidado con una clave b | Dos columnas, a.b y a: sobreviven los dos valores | Una columna, a.b, con el valor anidado; el valor de la clave literal se pierde | Una ruta con puntos no distingue un paso de un nombre que contiene un punto |
| Un array de escalares de primer nivel: [1,2,3] | Una única columna llamada value, con 1, 2 y 3 | Lo mismo: una única columna llamada value | Un escalar no tiene clave que sirva de nombre de columna; ahora las dos herramientas inventan la misma |
Preguntas frecuentes
- ¿Serializo los valores anidados o los aplano?
- Pregúntate quién abre el archivo. Si la respuesta es un programa que volverá a analizarlo, serializa: la forma del registro se conserva exacta y el viaje de ida y vuelta devuelve lo que entró. Si la respuesta es una persona en una hoja de cálculo, aplana: necesita ordenar por customer.city y sumar lines.0.qty, y no puede hacer ninguna de las dos cosas con un bloque JSON en una celda. Si la respuesta es ambas, produce dos archivos en lugar de un término medio, porque el término medio —aplanar un nivel y serializar el resto— es la versión que nadie sabrá interpretar seis meses después. Y si el archivo es un histórico y no un informe, serializa: aplanar fija las longitudes de array de hoy en el reparto de columnas, y la exportación del mes que viene no encajará.
- ¿Puedo obtener una fila por línea en vez de columnas extra?
- De ninguno de los dos conversores: ambos ensanchan la tabla y ninguno estalla un array en filas. La razón es que estallar no es una decisión de formato sino un cambio de grano: el archivo resultante responde a otra pregunta, y el conversor tendría que decidir qué array estallar cuando un registro contiene dos. Hazlo antes de convertir, en lo que produzca el JSON: emite un objeto por línea de pedido, cada uno con los campos del pedido que necesite. Entonces la pregunta del aplanado desaparece, porque el array ya no está. Si solo tienes el JSON, un script corto que asocie cada pedido con sus líneas y concatene los resultados son cinco líneas y deja la decisión visible en tu propio código en lugar de enterrada en los ajustes de una herramienta.
- ¿Por qué mi archivo ganó una columna entre dos exportaciones de la misma consulta?
- Porque el reparto de columnas de una exportación aplanada es una propiedad de los datos, no de la consulta. Las columnas son la unión de todas las rutas presentes, y las rutas de array se numeran hasta la longitud del array más largo del resultado. Un registro con cuatro etiquetas, donde la vez anterior había como mucho tres, añade tags.3 a todas las filas. El mismo mecanismo añade una columna cuando un solo registro contiene una clave opcional que nadie había usado antes. Dos defensas: leer las columnas por nombre y no por posición en todo lo que venga después, y, si un reparto estable importa de verdad, definir explícitamente la lista de columnas y proyectar sobre ella, en lugar de dejar que el exportador la deduzca de lo que hubiera en el conjunto de resultados.
- ¿Puedo devolver el CSV aplanado al JSON original?
- En parte, y las lagunas son previsibles. El aplanador de JSON tiene un modo reconstruir que rehace el anidamiento a partir de las claves-ruta: un segmento que es solo un número construye un array, cualquier otro construye un objeto, así que customer.name y tags.0 vuelven como objeto y array. Tres cosas no vuelven. Los tipos se han ido, porque cada celda de un CSV es texto: un número escrito 1 vuelve como la cadena "1" salvo que lo conviertas. La distinción entre null, cadena vacía y clave ausente se ha ido, como se vio arriba. Y un array vacío o un objeto vacío no deja ninguna ruta en un aplanado con puntos, así que no se puede reconstruir; el aplanador escribe un [] o un {} visible justo por eso, pero solo si aplanaste con esa herramienta. Ir y volver por la estrategia de serialización no pierde nada de esto, y ese es todo su argumento.
- Mi CSV usa punto y coma. ¿Tengo que convertirlo antes?
- No. El conversor CSV / JSON / YAML cuenta comas contra puntos y comas en el primer registro, fuera de los campos entrecomillados, y se queda con el ganador: una exportación de Excel francesa, alemana, española, italiana o portuguesa se lee bien sin tocar nada. El caso que sigue fallando es la tabulación o la barra vertical: ninguna de las dos está entre los candidatos que puntúa, así que un archivo separado por tabulaciones da un solo campo por fila, cuya clave es toda tu línea de cabecera. El síntoma es inconfundible una vez lo conoces: una única clave con tabulaciones en el nombre. Dos salidas. Convierte primero el delimitador con el conversor de delimitadores, que analiza como es debido y vuelve a entrecomillar lo que haga falta. O usa la herramienta específica de CSV a JSON, que pesa cuatro candidatos y te deja forzar uno. Ten en cuenta además que, entre lo que entre, el CSV que este conversor escribe de vuelta va separado por comas.
Artículos que podrían interesarte
Todas las guías →Herramientas relacionadas
Esto describe lo que hacen estos conversores hoy, comprobado ejecutándolos, y no lo que una norma obligue a hacer a un conversor. El CSV no tiene norma prescriptiva: el RFC 4180 es informativo y describe una práctica habitual, así que dos herramientas aparentemente correctas pueden discrepar sobre el mismo archivo sin que ninguna se equivoque. El aplanado, la detección de tipos y la de arrays son convenciones, no reglas. Antes de convertir datos que no puedas volver a exportar, pasa primero por una copia y compara el número de filas y columnas en ambos extremos.
Fuentes
- IETF — RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format — section 4 on objects, whose names may be any string including one containing a dot, and section 5 on arrays being ordered sequences with no declared length
- Ecma International — ECMA-404, The JSON Data Interchange Syntax, 2nd edition — the grammar alone, with no schema layer and therefore no notion of a required key or a fixed array length
- IETF — RFC 6901, JavaScript Object Notation (JSON) Pointer — the slash-separated path syntax for addressing a value inside a JSON document, and the escaping it defines for a name that contains the separator
- IETF — RFC 4180, Common Format and MIME Type for Comma-Separated Values (CSV) Files — the format on the other side of the conversion, which has no way to record a type, a null, or the shape a value had before it was flattened
¿Has detectado un error en este artículo?