Saltar al contenido
AldeaCode Logo
CSV a JSON / jq Formato 100% local

CSV a JSON con jq: @csv, modo slurp, cabeceras y quoting

jq es el martillo correcto para JSON en la shell. Convierte CSV simple a JSON en un solo pipeline, pero el quoting RFC 4180 (comas dentro de campos entre comillas) está sinceramente más allá de lo que jq plano puede parsear. Saber dónde está esa línea mantiene tus scripts honestos.

El pipeline básico de CSV a JSON

Para entradas separadas por coma sin comas entre comillas, jq más un truco con la fila de cabecera basta:

jq -R -s '
  split("\n")
  | map(select(length > 0))
  | (.[0] | split(",")) as $headers
  | .[1:]
  | map(split(",") | [$headers, .] | transpose | map({(.[0]): .[1]}) | add)
' data.csv

-R lee texto crudo en vez de intentar parsear JSON. -s traga el archivo entero como un único string para que podamos partir por saltos de línea. El truco de transpose y map junta cabeceras con cada fila en un objeto.

Funciona perfecto para IDs, nombres, números como strings. Se rompe en cuanto un campo contiene una coma literal, que es la mayoría de CSV reales.

Por qué jq plano no puede con RFC 4180 entero

El quoting de RFC 4180 (un campo entre comillas dobles que a su vez contiene comas, saltos o comillas duplicadas) exige un parser sensible al contexto. split(",") no lo es: no sabe si la siguiente coma está dentro de comillas.

Puedes apañar un split con regex y lookbehind negativo, pero el motor regex de jq es Oniguruma y aunque soporta lookbehind, el resultado queda frágil y lento. La respuesta honesta es: no parsees CSV RFC 4180 con jq plano.

Dos salidas limpias:

1. Pre-limpia el CSV con csvkit (csvjson data.csv) y mete eso en jq para el lado JSON. Mejor para datos con quoting. 2. Usa un delimitador estricto que no aparezca en tus datos (tab, barra) y parte por ahí. Práctico para pipelines de export que controlas tú.

JSON a CSV con @csv es el camino fácil

Ir al revés es directo porque el filtro @csv de jq se encarga del quoting él solo:

jq -r '
  (map(keys) | add | unique) as $cols
  | $cols, (.[] | [.[ $cols[] ]])
  | @csv
' data.json

@csv mete entre comillas cualquier campo con coma, salto o comilla, y duplica las comillas internas según RFC 4180. La salida es segura para meter en Excel, en COPY de Postgres o en cualquier consumidor CSV moderno.

La primera línea emite la cabecera construida con la unión de todas las claves; el resto proyecta cada objeto en el mismo orden de columnas. Si tus objetos tienen formas distintas, esta es la estrategia de cabecera más limpia.

Cuándo jq no toca y csvkit sí

Si te encuentras escapando comas, duplicando backslashes o escribiendo un programa jq de 40 líneas para gestionar casos raros de quoting, para. Usa csvkit:

```bash pip install csvkit

# CSV a JSON, con conciencia de RFC 4180, inferencia de cabecera, detección de tipos csvjson --no-inference data.csv > data.json

# JSON a CSV, con orden de columnas in2csv data.json > data.csv ```

csvkit se apoya en el módulo csv de la stdlib de Python, que cumple RFC 4180. El pipeline queda: csvkit para el lado CSV, jq para el lado JSON, ambos en el mismo pipe de shell. Esa división del trabajo cubre cualquier caso raro que veas en pipelines reales sin escribir parsers.

Ejemplo completo

bash
#!/usr/bin/env bash
# CSV con campos simples, sin comillas, a JSON
set -euo pipefail

jq -R -s '
  split("\n")
  | map(select(length > 0))
  | (.[0] | split(",")) as $headers
  | .[1:]
  | map(split(",")
      | [$headers, .] | transpose
      | map({(.[0]): .[1]}) | add)
' data.csv > data.json

# Para CSV con comas entre comillas, mejor csvkit:
#   csvjson data.csv > data.json

¿Solo necesitas el resultado?

Cuando tienes un CSV puntual y escribir el pipeline jq tarda más que la conversión, pega el CSV en el convertidor CSV a JSON del navegador. Quoting, BOM e inferencia de cabecera están resueltos, el JSON sale, lo copias al siguiente pipeline. Local, sin upload, con RFC 4180 entendido.

Abrir Convertidor CSV ↔ JSON →

Preguntas frecuentes

¿jq lee TSV?

Sí. Cambia split(",") por split("\t") en el pipeline de arriba. El tabulador es el delimitador menos probable de aparecer en tus datos, así que el truco de parseo aguanta casos RFC 4180 donde la coma no.

¿jq lee varios CSV a la vez?

jq lee un solo flujo. Concatena los archivos con cat antes, pero asegúrate de que solo el primero tiene cabecera, o quítala al resto con tail antes de meterla. Si no, las cabeceras se vuelven filas.

¿Por qué mi columna numérica acaba como strings en JSON?

Porque split devuelve strings. Castea en jq con tonumber dentro del map, o pasa el resultado por csvkit, que infiere tipos. Forzar strings a veces es correcto (códigos postales con cero a la izquierda), así que el default es conservador.