JSON a CSV
Objetos y arrays anidados tratados de forma explícita, sin límite de tamaño y sin subida.
Nada de lo que pegues sale de tu navegador. La lista de permitidos de connect-src convierte eso en una garantía del navegador, no en una promesa. Compruébalo tú mismo
El CSV es rectangular y el JSON es un árbol, así que todo conversor tiene que tomar una serie de decisiones discutibles. La mayoría las toma en silencio. Esta página las toma, las declara y te deja cambiarlas.
No hay límite de tamaño de archivo ni cuota diaria, porque no hay ningún servidor que pueda imponerla.
Las columnas son la unión, no el primer objeto
La decisión con más consecuencias de todas. Un conversor que lee las claves del elemento cero descarta en silencio todos los campos que solo aparecen en registros posteriores, y lo descubres aguas abajo, cuando falta una columna en un informe.
Este recoge todas las rutas de todas las filas, en el orden en que apareció cada una. Es más lento y es correcto. Papa Parse, la biblioteca CSV más popular de JavaScript, toma el primer objeto por defecto y necesita una opción columns explícita para hacer otra cosa, algo que conviene saber si te estás construyendo esto tú.
Arrays dentro de una fila: cuatro políticas, un valor por defecto
Este es el caso genuinamente ambiguo, y no hay una respuesta correcta, solo un valor por defecto correcto.
- Columnas indexadas, el valor por defecto
- tags.0, tags.1 y así sucesivamente. Sin pérdida y con ida y vuelta. Un único array largo dispara el número de columnas, y ese es el coste.
- Unir en una celda
- Valores unidos con un punto y coma. Legible, con pérdida, e inseguro si algún valor contiene el separador.
- JSON en una celda
- El array serializado como texto JSON. Sin pérdida y compacto, pero hay que volver a parsearlo después.
- Explotar en filas
- Una fila de salida por cada elemento del array, repitiendo las columnas escalares. Esto es el record_path de json_normalize de pandas. Correcto para una relación de uno a muchos y equivocado para todo lo demás, así que hay que activarlo y se aplica a una ruta concreta.
Dos cosas sobre Excel que no son opcionales
- La marca de orden de bytes UTF-8
- Excel no detecta UTF-8 en un CSV. Sin un BOM al principio del archivo lee los bytes en la página de códigos del sistema y todos los caracteres acentuados y los emojis llegan corrompidos. Por eso el BOM está activado por defecto aquí, y desactivarlo está a un clic para los pipelines que se atragantan con él.
- Inyección de fórmulas
- Una celda que empieza por =, +, - o @ la ejecutan como fórmula Excel, Google Sheets y LibreOffice. Un valor de =HYPERLINK("http://evil","clic") en un CSV que has generado tú se convierte en un enlace activo en la hoja de cálculo de otra persona. OWASP llama a esto inyección CSV. Por defecto, esas celdas reciben un apóstrofo delante, y la herramienta te avisa cuando lo ha hecho.
null frente a cadena vacía
En JSON son valores distintos y Excel muestra los dos en blanco, así que la mayoría de conversores los aplanan juntos y la distinción se pierde. Aquí null se convierte en una celda vacía sin comillas y la cadena vacía en una celda vacía entrecomillada, lo que hace que el ida y vuelta sobreviva. Cuesta dos caracteres por cadena vacía y merece la pena.
How to do this in code
Convertir en código, con los argumentos que deciden si es correcto.
py Python, pandas
encoding="utf-8-sig" es la forma que tiene pandas de escribir el BOM que Excel necesita. Un utf-8 pelado produce un archivo que Excel lee mal.
import pandas as pd
# Flatten nested objects to dotted columns
df = pd.json_normalize(records)
df.to_csv('out.csv', index=False, encoding='utf-8-sig')
# One row per element of a nested array
df = pd.json_normalize(records, record_path='items', meta=['id']) sh jq
Usa @csv en vez de interpolación de cadenas. Se encarga por ti de las reglas de entrecomillado.
# Union of keys as the header, then the rows
jq -r '(map(keys) | add | unique) as $c
| $c, (.[] | [.[$c[]]])
| @csv' records.json > out.csv
# @csv quotes and escapes correctly; @text does not js JavaScript
import Papa from 'papaparse';
// Pass the union explicitly. Without it, Papa takes the keys of
// the first object and silently drops the rest.
const columns = [...new Set(records.flatMap(Object.keys))];
const csv = Papa.unparse(records, { columns }); go Go
w := csv.NewWriter(f)
w.Write(columns)
for _, rec := range records {
row := make([]string, len(columns))
for i, c := range columns {
row[i] = fmt.Sprint(rec[c])
}
w.Write(row)
}
w.Flush() Preguntas frecuentes
- ¿Por qué mi CSV tiene caracteres raros en Excel?
- El archivo es UTF-8 y Excel lo ha leído como la página de códigos del sistema. Deja activada la opción de marca de orden de bytes. Si el archivo va a algún sitio que no sea una hoja de cálculo, desactívala, ya que algunos parsers tratan el BOM como parte del nombre de la primera columna.
- ¿Por qué algunas celdas empiezan con un apóstrofo?
- Porque empezaban por =, +, - o @, que las hojas de cálculo ejecutan como fórmulas. El apóstrofo lo neutraliza. Desactiva la opción si necesitas el valor crudo y te fías de dónde va a acabar el archivo.
- ¿Y si mis datos no son un array de objetos?
- Un objeto suelto se convierte en una fila. Un array de escalares se convierte en una columna. Un envoltorio como {"data": [...]} usa el array interior y te dice que ha tomado esa decisión, porque es una suposición y no una regla.
- ¿Qué delimitador para el Excel europeo?
- Punto y coma. Excel elige su delimitador a partir del separador de listas del sistema, que es un punto y coma en las configuraciones regionales donde la coma es el separador decimal. Por eso un archivo separado por comas se abre como una sola columna en una máquina alemana o francesa.