CSV to JSON JavaScript — Converter + Code Examples

·Front-end & Node.js Developer·Revisado porSophie Laurent·Publicado

Usa el CSV to JSON gratuito directamente en tu navegador — sin instalación.

Probar CSV to JSON online →

La mayoría de los datos CSV que encuentro llegan como un string plano desde una subida de archivo, una exportación de base de datos o una API que aún habla el formato de los años 70. Para convertir CSV a JSON en JavaScript, necesitas dos cosas que el lenguaje te da gratis: la división de strings para analizar las filas y JSON.stringify() para serializar el resultado. No se necesitan paquetes npm para lo básico — esta guía cubre el proceso completo desde una utilidad csvToJson() reutilizable hasta PapaParse y la lectura/escritura de archivos en Node.js. Para conversiones rápidas sin código, el conversor CSV a JSON en línea lo resuelve al instante. Todos los ejemplos apuntan a Node.js 18+ y navegadores modernos.

  • Divide el CSV por salto de línea, extrae las cabeceras de la fila 0, mapea las filas restantes a objetos y usa JSON.stringify(array, null, 2) para una salida formateada.
  • JSON.stringify() produce un string; JSON.parse() lo convierte de vuelta a un array JavaScript vivo — identifica cuál tienes antes de operar sobre él.
  • Las instancias de Map no se serializan a JSON automáticamente — llama a Object.fromEntries(map) primero.
  • Para CSV con campos entrecomillados, comas dentro de valores o saltos de línea en celdas, usa PapaParse o csv-parse en lugar de la división manual.
  • csvtojson (npm) maneja la conversión de tipos, el streaming y los casos extremos de RFC 4180 en una sola llamada.

¿Qué es la conversión de CSV a JSON?

La conversión de CSV a JSON transforma un formato de texto plano delimitado por comas en un array estructurado de objetos donde cada fila se convierte en un objeto JavaScript con las cabeceras de columna como claves. El formato CSV no tiene tipos de datos — todo es un string. JSON agrega estructura, anidamiento y tipos explícitos (números, booleanos, null). Esta conversión es el primer paso en casi todos los pipelines de datos que comienzan con una exportación de hoja de cálculo, un volcado de sistema heredado o un archivo subido por el usuario. Los datos subyacentes permanecen igual; el formato cambia de columnas basadas en posición a propiedades con nombre.

Before · json
After · json
nombre,email,rol,activo
Carlos García,cgarcia@nexuslabs.io,Jefe de Ingeniería,true
Ana López,alopez@nexuslabs.io,Gerente de Producto,true
[
  {
    "nombre": "Carlos García",
    "email": "cgarcia@nexuslabs.io",
    "rol": "Jefe de Ingeniería",
    "activo": "true"
  },
  {
    "nombre": "Ana López",
    "email": "alopez@nexuslabs.io",
    "rol": "Gerente de Producto",
    "activo": "true"
  }
]

csvToJson() — Construir una función de conversión reutilizable

El proceso completo de CSV a JSON en JavaScript se divide en tres pasos: divide el string CSV por salto de línea para obtener las filas, extrae las cabeceras de la primera fila con split(','), luego mapea cada fila restante a un objeto JavaScript plano donde las claves provienen de las cabeceras y los valores de las posiciones de columna correspondientes. La llamada final a JSON.stringify() convierte ese array de objetos a un string JSON. Aquí hay una versión mínima funcional:

JavaScript — csvToJson mínimo
function csvToJson(csv) {
  const lines = csv.trim().split('\n')
  const headers = lines[0].split(',').map(h => h.trim())

  const rows = lines.slice(1)
    .filter(line => line.trim() !== '')
    .map(line => {
      const values = line.split(',')
      return Object.fromEntries(
        headers.map((header, i) => [header, values[i]?.trim()])
      )
    })

  return JSON.stringify(rows, null, 2)
}

const csv = `servidor,puerto,region,estado
api-gateway,8080,us-east-1,activo
auth-service,8443,eu-west-1,degradado
payments-api,9090,ap-south-1,activo`

console.log(csvToJson(csv))
// [
//   { "servidor": "api-gateway", "puerto": "8080", "region": "us-east-1", "estado": "activo" },
//   { "servidor": "auth-service", "puerto": "8443", "region": "eu-west-1", "estado": "degradado" },
//   { "servidor": "payments-api", "puerto": "9090", "region": "ap-south-1", "estado": "activo" }
// ]

Esa función maneja lo básico: saltos de línea al final, líneas vacías, espacios alrededor de los valores. Cada campo CSV llega como un string. Nótese que puerto es "8080" (un string), no 8080 (un número). Si necesitas tipos correctos en la salida JSON, tienes que convertirlos tú mismo. Aquí hay una versión ampliada con detección de tipos:

JavaScript — csvToJson con conversión de tipos
function convertirValor(val) {
  if (val === undefined || val === '') return null
  if (val === 'true') return true
  if (val === 'false') return false
  if (val === 'null') return null
  const num = Number(val)
  if (!isNaN(num) && val.trim() !== '') return num
  return val
}

function csvToJson(csv, { coerce = false } = {}) {
  const lines = csv.trim().split('\n')
  const headers = lines[0].split(',').map(h => h.trim())

  const rows = lines.slice(1)
    .filter(line => line.trim() !== '')
    .map(line => {
      const values = line.split(',')
      return Object.fromEntries(
        headers.map((header, i) => [
          header,
          coerce ? convertirValor(values[i]?.trim()) : values[i]?.trim()
        ])
      )
    })

  return JSON.stringify(rows, null, 2)
}

const csv = `endpoint,port,max_connections,debug
/api/v2/orders,8443,500,true
/api/v2/health,8080,100,false`

console.log(csvToJson(csv, { coerce: true }))
// [
//   { "endpoint": "/api/v2/orders", "port": 8443, "max_connections": 500, "debug": true },
//   { "endpoint": "/api/v2/health", "port": 8080, "max_connections": 100, "debug": false }
// ]

La opción coerce es opt-in porque la detección automática de tipos puede fallar — un campo como un código postal ("28001") pierde su cero inicial al convertirse a número. Mantén la conversión desactivada por defecto y actívala solo cuando controles el esquema. Nota rápida: JSON.stringify() acepta un tercer argumento space para la indentación. Pasa 2 para dos espacios, 4 para cuatro, o "\t" para tabulaciones. Omítelo completamente para una salida compacta en una sola línea — útil cuando envías el string JSON como cuerpo de una petición API donde los espacios en blanco solo desperdician ancho de banda.

Nota:Un string CSV sin procesar no es JSON. Llamar a JSON.parse() directamente sobre texto CSV lanza un SyntaxError. Primero debes convertir el CSV a objetos JavaScript con tu función csvToJson(), que internamente llama a JSON.stringify() para producir el string JSON real.

Manejo de Maps, Dates y objetos personalizados a partir de datos CSV

No toda conversión de CSV termina con un array plano de objetos simples. A veces necesitas construir un Map a partir de pares cabecera-valor, analizar strings de fecha en objetos Date, o adjuntar propiedades calculadas antes de la serialización. JavaScript tiene una particularidad que confunde a la gente: las instancias de Map no se serializan con JSON.stringify(). Obtienes un objeto vacío. La solución es Object.fromEntries() para convertir el Map de vuelta a un objeto plano antes de serializar.

La razón por la que Map serializa a {} es que JSON.stringify() itera sobre las propiedades enumerables propias de un objeto. Un Map almacena sus entradas en un slot interno, no como propiedades enumerables en el objeto en sí, por lo que el serializador ve un objeto sin claves. El prototipo de Map tampoco tiene un método toJSON(), que es el gancho que JSON.stringify() llama primero en cualquier valor antes de decidir cómo serializarlo. Si un valor tiene toJSON(), el valor de retorno del método es lo que se serializa, no el objeto en sí. Por eso los objetos Date se serializan correctamente: Date.prototype.toJSON devuelve un string ISO 8601, por lo que JSON.stringify(new Date()) produce una marca de tiempo entrecomillada en lugar de un objeto vacío. Entender este gancho te permite definir el mismo comportamiento en tus propias clases — como se muestra en el ejemplo RegistroEmpleado a continuación — para controlar exactamente qué campos derivados del CSV aparecen en la salida JSON final.

Convertir un Map a JSON

JavaScript — Map a JSON vía Object.fromEntries()
// Construir un Map a partir de pares cabecera→valor de CSV
const headers = ['servidor', 'puerto', 'region']
const values = ['payments-api', '9090', 'ap-south-1']
const rowMap = new Map(headers.map((h, i) => [h, values[i]]))

// Map NO serializa directamente
console.log(JSON.stringify(rowMap))
// "{}"  — objeto vacío, ¡datos perdidos!

// Convertir a objeto plano primero
const rowObj = Object.fromEntries(rowMap)
console.log(JSON.stringify(rowObj, null, 2))
// {
//   "servidor": "payments-api",
//   "puerto": "9090",
//   "region": "ap-south-1"
// }

Strings de fecha y toJSON()

Los campos de fecha en CSV llegan como strings. Si los analizas en objetos Date durante el procesamiento, esos Dates se serializan correctamente porque Date tiene un método toJSON() integrado que devuelve un string ISO 8601. También puedes definir toJSON() personalizado en tus propias clases para controlar qué columnas CSV aparecen en la salida serializada — por ejemplo, omitiendo campos de seguimiento internos como _rowIndex.

JavaScript — toJSON() para serialización personalizada
class RegistroEmpleado {
  constructor(csvRow) {
    this._rowIndex = csvRow._rowIndex  // interno, no para JSON
    this.empleadoId = csvRow.employee_id
    this.nombre = csvRow.nombre
    this.contratadoEn = new Date(csvRow.fecha_contratacion)
    this.salario = Number(csvRow.salario)
  }

  toJSON() {
    // Solo exponer los campos que queremos en la salida JSON
    return {
      employee_id: this.empleadoId,
      nombre: this.nombre,
      contratado_en: this.contratadoEn,  // Date.toJSON() → ISO string automáticamente
      salario: this.salario,
    }
  }
}

const csvRow = {
  _rowIndex: 42,
  employee_id: 'EMP-2847',
  nombre: 'Carlos García',
  fecha_contratacion: '2024-03-15',
  salario: '128000',
}

const record = new RegistroEmpleado(csvRow)
console.log(JSON.stringify(record, null, 2))
// {
//   "employee_id": "EMP-2847",
//   "nombre": "Carlos García",
//   "contratado_en": "2024-03-15T00:00:00.000Z",
//   "salario": 128000
// }
// Nota: _rowIndex está excluido, salario es un número, fecha en formato ISO

Reviver para deserialización de fechas

Después de escribir el JSON derivado de CSV en un archivo o enviarlo por la red, JSON.parse() te devuelve objetos planos — los objetos Date se convierten de nuevo en strings. Usa una función reviver para convertir los strings ISO 8601 de vuelta en objetos Date durante el análisis:

JavaScript — reviver para reconstruir objetos Date
const jsonString = '{"employee_id":"EMP-2847","nombre":"Carlos García","contratado_en":"2024-03-15T00:00:00.000Z","salario":128000}'

const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/

const parsed = JSON.parse(jsonString, (key, value) => {
  if (typeof value === 'string' && isoDatePattern.test(value)) {
    return new Date(value)
  }
  return value
})

console.log(parsed.contratado_en instanceof Date)  // true
console.log(parsed.contratado_en.getFullYear())    // 2024
Advertencia:JSON.stringify() devuelve undefined (no un string) para valores que contienen funciones, Symbols o propiedades undefined. Si tus objetos derivados de CSV recogen accidentalmente un valor undefined por una columna faltante, esa propiedad desaparece silenciosamente de la salida JSON. Usa siempre null como valor por defecto para los valores faltantes.

Referencia de parámetros de JSON.stringify()

La firma de la función es JSON.stringify(value, replacer?, space?). Los argumentos replacer y space son opcionales — pasa null para el replacer cuando solo necesitas indentación.

Parámetro
Tipo
Por defecto
Descripción
value
any
(requerido)
El valor JavaScript a serializar — objeto, array, string, número, booleano o null
replacer
Function | Array | null
undefined
Filtra o transforma valores durante la serialización. Un array incluye solo esas propiedades; una función recibe pares (key, value)
space
number | string
undefined
Indentación: número 0–10 para espacios, string (p. ej. "\t") para sangría personalizada. Omitir para salida compacta en una sola línea

Parámetros de JSON.parse():

Parámetro
Tipo
Por defecto
Descripción
text
string
(requerido)
El string JSON a analizar — debe ser JSON válido o se lanza un SyntaxError
reviver
Function | undefined
undefined
Se llama para cada par clave-valor. El valor retornado reemplaza al original; retornar undefined elimina la propiedad

JSON.parse() — Consumir la salida JSON

Una vez que tienes un string JSON de csvToJson(), el siguiente paso suele ser analizarlo de vuelta a un array JavaScript vivo para filtrar, mapear o alimentar una API. La diferencia entre un string JSON (typeof === "string") y un objeto JavaScript importa. No puedes llamar a .filter() ni acceder a [0].nombre sobre un string — necesitas JSON.parse() primero. Este ciclo completo (stringify y luego parse) también funciona como técnica de validación: si tu conversión de CSV produce algo que no es JSON válido, parse lanzará un error. El argumento opcional reviver te permite transformar cada par clave-valor durante el análisis — útil para restaurar objetos Date desde strings ISO o renombrar claves sin un paso adicional.

JavaScript — analizar salida JSON y consultar filas
const csv = `endpoint,method,avg_latency_ms,error_rate
/api/v2/orders,POST,342,0.02
/api/v2/health,GET,12,0.00
/api/v2/payments,POST,890,0.15
/api/v2/users,GET,45,0.01`

// Paso 1: convertir CSV a string JSON
const jsonString = csvToJson(csv, { coerce: true })

// Paso 2: analizar de vuelta a array JavaScript
const endpoints = JSON.parse(jsonString)

// Verificar que es un array
console.log(Array.isArray(endpoints))  // true

// Filtrar endpoints con alta latencia
const lentos = endpoints.filter(ep => ep.avg_latency_ms > 200)
console.log(lentos.map(ep => ep.endpoint))
// ["/api/v2/orders", "/api/v2/payments"]

// Desestructurar la primera fila
const [primero, ...resto] = endpoints
console.log(primero.endpoint)  // "/api/v2/orders"
console.log(resto.length)      // 3

Un wrapper seguro para JSON.parse() es útil cuando se valida la salida de conversión antes del procesamiento posterior. Si la conversión de CSV produce JSON malformado por alguna razón (entrada truncada, errores de codificación), esto lo captura sin que el proceso falle:

JavaScript — wrapper de parse seguro
function parseSafe(jsonString) {
  try {
    return { data: JSON.parse(jsonString), error: null }
  } catch (err) {
    return { data: null, error: err.message }
  }
}

// Salida válida
const result = parseSafe(csvToJson(csv))
if (result.error) {
  console.error('La conversión CSV produjo JSON inválido:', result.error)
} else {
  console.log(`Se analizaron ${result.data.length} filas`)
}

// Pasar CSV sin procesar a JSON.parse — esto falla
const malo = parseSafe('nombre,email\nCarlos,cgarcia@nexuslabs.io')
console.log(malo.error)  // "Unexpected token 'n', "nombre,email"... is not valid JSON"

Reviver para renombrar claves y validar

La función reviver recibe cada par clave-valor durante el análisis, desde las propiedades más internas hacia afuera. Devolver undefined para una clave la elimina completamente del resultado; devolver un valor diferente lo reemplaza. El reviver es útil para renombrar cabeceras (camelCase a snake_case), eliminar campos internos, o verificar que existan columnas requeridas. Se llama con el valor raíz al final (clave de string vacío), que es donde lanzas un error si el resultado no es un array.

JavaScript — reviver para renombrar claves y validar forma
const jsonString = csvToJson(`empleadoId,nombreCompleto,fechaContratacion
EMP-2847,Carlos,2024-03-15
EMP-3012,Ana,2023-11-01`, { coerce: false })

const camelToSnake = str => str.replace(/[A-Z]/g, c => '_' + c.toLowerCase())

const empleados = JSON.parse(jsonString, function(key, value) {
  // Valor raíz — validar forma
  if (key === '') {
    if (!Array.isArray(value)) throw new Error('Se esperaba un array JSON del CSV')
    return value
  }
  // Renombrar claves camelCase a snake_case
  if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
    return Object.fromEntries(
      Object.entries(value).map(([k, v]) => [camelToSnake(k), v])
    )
  }
  return value
})

console.log(empleados[0])
// { empleado_id: 'EMP-2847', nombre_completo: 'Carlos', fecha_contratacion: '2024-03-15' }

Convertir CSV desde un archivo y una respuesta de API

Los dos lugares de donde realmente provienen los datos CSV en producción: archivos en disco y respuestas HTTP. Ambos escenarios necesitan manejo de errores porque la entrada es externa e incontrolada.

Leer archivo CSV, convertir, escribir JSON

Node.js 18+ — conversión de archivo
import { readFileSync, writeFileSync } from 'node:fs'

function csvToJsonDesdeArchivo(inputPath, outputPath) {
  let csvText
  try {
    csvText = readFileSync(inputPath, 'utf8')
  } catch (err) {
    throw new Error(`Error al leer ${inputPath}: ${err.message}`)
  }

  const lines = csvText.trim().split('\n')
  if (lines.length < 2) {
    throw new Error(`${inputPath} no tiene filas de datos (solo ${lines.length} línea)`)
  }

  const headers = lines[0].split(',').map(h => h.trim())
  const rows = lines.slice(1)
    .filter(line => line.trim() !== '')
    .map(line => {
      const values = line.split(',')
      return Object.fromEntries(headers.map((h, i) => [h, values[i]?.trim()]))
    })

  const jsonOutput = JSON.stringify(rows, null, 2)
  writeFileSync(outputPath, jsonOutput, 'utf8')
  console.log(`Convertidas ${rows.length} filas → ${outputPath}`)
  return rows
}

// Uso
const data = csvToJsonDesdeArchivo('inventario.csv', 'inventario.json')
console.log(data[0])
// { sku: "WDG-2847", almacen: "us-east-1", cantidad: "150", ... }

Obtener CSV desde un endpoint de API

Node.js 18+ — conversión de respuesta de API
async function fetchCsvComoJson(url) {
  const response = await fetch(url)
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`)
  }

  const contentType = response.headers.get('content-type') || ''
  if (!contentType.includes('text/csv') && !contentType.includes('text/plain')) {
    console.warn(`Content-type inesperado: ${contentType}`)
  }

  const csvText = await response.text()
  const lines = csvText.trim().split('\n')
  const headers = lines[0].split(',').map(h => h.trim())
  const rows = lines.slice(1)
    .filter(line => line.trim() !== '')
    .map(line => {
      const values = line.split(',')
      return Object.fromEntries(headers.map((h, i) => [h, values[i]?.trim()]))
    })

  return rows
}

// Ejemplo: obtener CSV de tipos de cambio desde un proveedor de datos
try {
  const rates = await fetchCsvComoJson('https://data.ecb.internal/rates/daily.csv')
  console.log(JSON.stringify(rates.slice(0, 3), null, 2))
  // Enviar como JSON al servicio posterior
  await fetch('https://api.internal/v2/rates', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ data: rates }),
  })
} catch (err) {
  console.error('Sincronización de tipos falló:', err.message)
}
Nota:El argumento replacer en JSON.stringify() te permite incluir solo columnas específicas del CSV. Pasa un array de nombres de cabeceras para incluir solo esos campos: JSON.stringify(rows, ['nombre', 'email', 'departamento']). Las propiedades que no están en el array se excluyen silenciosamente de la salida.

Conversión de CSV a JSON por línea de comandos

Node.js puede ejecutar scripts en línea, y existen herramientas CLI dedicadas que gestionan la conversión de CSV a JSON sin necesidad de escribir un script.

bash — Node.js one-liner
# Canalizar CSV a un script en línea de Node.js
cat servidores.csv | node -e "
  const lines = require('fs').readFileSync('/dev/stdin','utf8').trim().split('\n');
  const h = lines[0].split(',');
  const rows = lines.slice(1).map(l => Object.fromEntries(h.map((k,i) => [k.trim(), l.split(',')[i]?.trim()])));
  console.log(JSON.stringify(rows, null, 2));
"
bash — Miller (mlr) para CSV a JSON
# Miller es una navaja suiza para datos estructurados
# Instalar: brew install miller (macOS) o apt install miller (Debian/Ubuntu)
mlr --icsv --ojson cat inventario.csv

# Filtrar filas durante la conversión
mlr --icsv --ojson filter '$cantidad > 100' inventario.csv

# Seleccionar columnas específicas
mlr --icsv --ojson cut -f sku,almacen,cantidad inventario.csv
bash — csvtojson CLI
# Instalar globalmente
npm install -g csvtojson

# Convertir archivo
csvtojson servidores.csv > servidores.json

# Canalizar desde stdin
cat exports/metricas-t1.csv | csvtojson > metricas-t1.json

Para archivos grandes, Miller suele ser la mejor opción frente a csvtojson. Miller está implementado en C y procesa el CSV como un stream sin cargar el archivo completo en memoria, lo que significa que maneja exportaciones de varios gigabytes con un uso de memoria constante. También soporta operaciones a nivel de campo en línea — renombrar columnas, convertir tipos, filtrar filas — antes de que los datos se conviertan en JSON, evitando un pipeline de dos pasos: analizar y luego transformar. csvtojson, por otro lado, se ejecuta en Node.js y es más conveniente cuando el resto de tu cadena de herramientas es JavaScript: puedes canalizar su salida directamente en streams de Node, importarlo como librería o usar su API colParser para la conversión de tipos por columna en el código. Prefiere Miller para el máximo rendimiento y pipelines de shell; prefiere csvtojson cuando necesites integración estrecha con una aplicación Node.js.

Nota:jq no analiza CSV de forma nativa. Si necesitas jq en el pipeline, convierte a JSON primero con csvtojson o mlr, luego canaliza la salida JSON a jq para filtrar y transformar.

Alternativa de alto rendimiento — PapaParse

El enfoque manual con split(',') falla con archivos CSV del mundo real. Campos entrecomillados que contienen comas, saltos de línea embebidos, comillas dobles escapadas — todo esto rompe un divisor simple. PapaParse es la librería a la que recurro cuando el CSV proviene de una fuente desconocida. Maneja todos los casos extremos de RFC 4180, detecta delimitadores automáticamente y funciona tanto en Node.js como en navegadores.

bash — instalar PapaParse
npm install papaparse
JavaScript — PapaParse con conversión de tipos
import Papa from 'papaparse'

const csv = `producto,descripcion,precio,en_stock
"Widget, Grande","Un widget premium con características ""extra""",29.99,true
Conjunto de Tornillos,Kit estándar de tornillos M8,4.50,true
"Juego de Juntas","Incluye junta, sello y O-ring",12.75,false`

const { data, errors, meta } = Papa.parse(csv, {
  header: true,
  dynamicTyping: true,     // convierte automáticamente números y booleanos
  skipEmptyLines: true,
  transformHeader: h => h.trim().toLowerCase().replace(/\s+/g, '_'),
})

if (errors.length > 0) {
  console.error('Errores de análisis:', errors)
}

console.log(JSON.stringify(data, null, 2))
// [
//   {
//     "producto": "Widget, Grande",
//     "descripcion": "Un widget premium con características \"extra\"",
//     "precio": 29.99,
//     "en_stock": true
//   },
//   ...
// ]
console.log(`Se analizaron ${data.length} filas, delimitador: "${meta.delimiter}"`)

La opción dynamicTyping de PapaParse realiza la conversión de tipos automáticamente — los números se convierten en números, "true"/"false" en booleanos. El callback transformHeader normaliza los nombres de columna a snake_case, lo que te ahorra tener que lidiar con cabeceras inconsistentes de diferentes exportaciones CSV. Para conversiones rápidas sin escribir código de análisis, el conversor CSV a JSON lo gestiona todo en el navegador.

Salida en terminal con resaltado de sintaxis

Volcar un gran array JSON en el terminal cansa la vista rápidamente. Agregar resaltado de sintaxis a la salida la hace legible durante la depuración y el desarrollo. El paquete cli-highlight coloriza la salida JSON en terminales de Node.js.

bash — instalar cli-highlight
npm install cli-highlight
JavaScript — salida JSON con colores en terminal
import { highlight } from 'cli-highlight'

// Después de convertir CSV a array JSON
const jsonOutput = JSON.stringify(rows, null, 2)

// Imprimir con resaltado de sintaxis
console.log(highlight(jsonOutput, { language: 'json' }))
// Claves, strings, números y booleanos obtienen colores distintos

La salida con colores es muy útil cuando inspeccionas de forma interactiva un resultado de conversión grande. Las claves JSON, los valores de string, los números y los booleanos obtienen colores ANSI distintos, lo que facilita detectar un campo cuyo tipo es incorrecto — por ejemplo, un número de puerto que debería ser 8080 pero aparece destacado como un string porque la conversión de tipos estaba desactivada. Esto es especialmente útil al depurar archivos CSV exportados desde herramientas de hoja de cálculo donde los tipos de columna son inconsistentes entre filas. Sin color, escanear 50 filas de JSON en busca de un solo campo con tipo incorrecto implica leer cada valor individualmente. Con color, un número con el color de string salta a la vista de inmediato.

Advertencia:El resaltado de sintaxis agrega códigos de escape ANSI a la salida. No lo uses al escribir JSON en un archivo, al canalizar a otro programa o al enviarlo como cuerpo de una respuesta API — los códigos de escape corromperían el JSON. Usa el resaltado solo para visualización en terminal.

Trabajar con archivos CSV grandes

Cargar un archivo CSV de 500 MB en un string con readFileSync() consumirá memoria y podría bloquear tu proceso. Para archivos grandes, procesa el CSV línea por línea como un stream y emite objetos JSON a medida que llegan. El paquete csv-parse (parte del ecosistema csv en npm) proporciona un analizador en streaming que funciona con los streams de Node.js.

Streaming de CSV a NDJSON con csv-parse

NDJSON (JSON Delimitado por Saltos de Línea) es un formato donde cada línea del archivo de salida es un objeto JSON autocontenido. A diferencia de un gran array JSON único — que requiere tener el archivo completo en memoria antes de comenzar a leerlo — los archivos NDJSON se pueden procesar línea por línea. Esto hace que NDJSON sea ideal para grandes conjuntos de datos que serán consumidos por procesadores de registros, pipelines de stream o bases de datos con APIs de importación masiva. El paquete csv-parse emite un objeto JavaScript por fila CSV en modo objeto, por lo que puedes canalizarlo directamente a un stream de transformación que agrega \n después de cada JSON.stringify(row).

Node.js 18+ — streaming de CSV a NDJSON
import { createReadStream, createWriteStream } from 'node:fs'
import { parse } from 'csv-parse'
import { Transform } from 'node:stream'
import { pipeline } from 'node:stream/promises'

// Transformar cada objeto de fila CSV a una línea JSON
const toNdjson = new Transform({
  objectMode: true,
  transform(record, encoding, callback) {
    callback(null, JSON.stringify(record) + '\n')
  },
})

await pipeline(
  createReadStream('telemetria-2026-03.csv'),
  parse({
    columns: true,       // usar primera fila como cabeceras
    skip_empty_lines: true,
    trim: true,
    cast: true,          // convertir automáticamente números y booleanos
  }),
  toNdjson,
  createWriteStream('telemetria-2026-03.ndjson')
)

console.log('Conversión en streaming completada')
// Cada línea del archivo de salida es un objeto JSON:
// {"timestamp":"2026-03-15T08:22:00Z","service":"gateway","latency_ms":42,"status":200}
// {"timestamp":"2026-03-15T08:22:01Z","service":"auth","latency_ms":156,"status":401}
// ...

Streaming con PapaParse para navegador y Node.js

El modo streaming de PapaParse usa un callback step que se activa una vez por fila en lugar de recopilar todas las filas en memoria. Le pasas un ReadStream de Node.js (en Node.js) o un objeto File (en el navegador) y PapaParse maneja el fragmentado internamente. No es necesario configurar un pipeline de stream — solo un callback. Úsalo cuando necesites conformidad con RFC 4180 sin incorporar csv-parse.

Node.js — PapaParse en streaming para archivos grandes
import Papa from 'papaparse'
import { createReadStream } from 'node:fs'

let rowCount = 0
const errors = []

const fileStream = createReadStream('inventario-almacen.csv')

Papa.parse(fileStream, {
  header: true,
  dynamicTyping: true,
  step(result) {
    // Procesar una fila a la vez — memoria constante
    rowCount++
    if (result.data.cantidad === 0) {
      errors.push(`Fila ${rowCount}: ${result.data.sku} sin existencias`)
    }
  },
  complete() {
    console.log(`Procesadas ${rowCount} filas`)
    if (errors.length > 0) {
      console.log(`Problemas encontrados: ${errors.length}`)
      errors.forEach(e => console.log(`  ${e}`))
    }
  },
  error(err) {
    console.error('Análisis fallido:', err.message)
  },
})
Nota:Cambia a streaming cuando tu archivo CSV supere los 50 MB o cuando estés procesando una entrada sin límite (feed de WebSocket, server-sent events, stdin canalizado). El formato NDJSON (un objeto JSON por línea) suele ser un mejor formato de salida que un gran array JSON para conjuntos de datos grandes — se puede procesar línea por línea sin cargar el archivo completo en memoria.

Errores comunes

Llamar a JSON.parse() directamente sobre un string CSV

Problema: CSV no es JSON. Pasar un string CSV sin procesar a JSON.parse() lanza un SyntaxError porque las comas y los saltos de línea no son sintaxis JSON válida.

Solución: Analiza primero el CSV en objetos JavaScript usando split() o una librería, luego usa JSON.stringify() para producir JSON. Llama a JSON.parse() solo sobre strings que ya son JSON válido.

Before · JavaScript
After · JavaScript
const csv = 'nombre,email\nCarlos García,cgarcia@nexuslabs.io'
const data = JSON.parse(csv)
// SyntaxError: Unexpected token 'n'
const csv = 'nombre,email\nCarlos García,cgarcia@nexuslabs.io'
const lines = csv.trim().split('\n')
const headers = lines[0].split(',')
const rows = lines.slice(1).map(line =>
  Object.fromEntries(headers.map((h, i) => [h, line.split(',')[i]]))
)
const json = JSON.stringify(rows, null, 2)  // string JSON válido
Usar toString() en lugar de JSON.stringify()

Problema: Llamar a toString() sobre un objeto JavaScript devuelve el string inútil '[object Object]' en lugar de los datos reales. Esto destruye silenciosamente tu salida de CSV a JSON.

Solución: Usa siempre JSON.stringify() para convertir objetos JavaScript a strings JSON. toString() existe para la conversión de primitivos a string, no para la serialización.

Before · JavaScript
After · JavaScript
const fila = { servidor: 'api-gateway', puerto: 8080 }
const output = fila.toString()
// "[object Object]"  — los datos se han perdido
const fila = { servidor: 'api-gateway', puerto: 8080 }
const output = JSON.stringify(fila, null, 2)
// '{"servidor":"api-gateway","puerto":8080}'
Dividir por coma sin manejar campos entrecomillados

Problema: Un split(",") simple falla cuando los valores CSV contienen comas dentro de campos entrecomillados: "Widget, Grande" se convierte en dos valores separados en lugar de uno.

Solución: Usa PapaParse o csv-parse para cualquier dato CSV que no controles completamente. Si debes analizar manualmente, implementa un analizador de máquina de estados que rastree si la posición actual está dentro de un campo entrecomillado.

Before · JavaScript
After · JavaScript
const line = '"Widget, Grande","Calidad premium",29.99'
const values = line.split(',')
// ["\"Widget", " Grande\"", "\"Calidad premium\"", "29.99"]
// 4 valores en lugar de 3 — primer campo dividido incorrectamente
import Papa from 'papaparse'
const { data } = Papa.parse('"Widget, Grande","Calidad premium",29.99')
// data[0] = ["Widget, Grande", "Calidad premium", "29.99"]
// 3 valores, correctamente analizados
Olvidar que todos los valores CSV son strings

Problema: Sin conversión de tipos, port: "8080" permanece como string en JSON en lugar de un número. Los sistemas posteriores que esperan tipos numéricos rechazan o manejan incorrectamente los datos.

Solución: Aplica conversión de tipos explícita durante el paso de mapeo, o usa PapaParse con dynamicTyping: true. Sé siempre deliberado sobre qué campos deben ser numéricos.

Before · JavaScript
After · JavaScript
const fila = { puerto: '8443', debug: 'true', workers: '4' }
JSON.stringify(fila)
// {"puerto":"8443","debug":"true","workers":"4"}  — todos strings
const fila = {
  puerto: Number('8443'),          // 8443
  debug: 'true' === 'true',       // true
  workers: Number('4'),            // 4
}
JSON.stringify(fila)
// {"puerto":8443,"debug":true,"workers":4}  — tipos correctos

Análisis manual vs librerías — comparación rápida

Método
Salida formateada
JSON válido
Tipos personalizados
Streaming
Requiere instalación
JSON.stringify()
✓ (con space)
✓ vía toJSON()/replacer
No (integrado)
JSON.parse()
N/A (parsing)
✓ (valida)
✓ vía reviver
No (integrado)
csv-parse (Node.js)
✗ (retorna objetos)
✗ (no es JSON)
npm install
PapaParse
✗ (retorna objetos)
✗ (no es JSON)
npm install
csvtojson
✓ vía colParser
npm install
jq (CLI)
N/A
Instalación del sistema
Miller (CLI)
N/A
Instalación del sistema

Para scripts rápidos donde controlas el formato CSV y sabes que no hay campos entrecomillados, el enfoque integrado de split() + JSON.stringify() funciona y no requiere dependencias. Para sistemas de producción que procesan archivos CSV subidos por usuarios, usa PapaParse en el navegador o csv-parse en Node.js — ambos manejan RFC 4180 correctamente y soportan streaming. El paquete csvtojson es el único que produce JSON directamente, gestionando tanto el análisis como la serialización en una sola llamada. Cuando necesites el camino más rápido desde pegar hasta obtener el resultado, el conversor CSV a JSON lo gestiona todo en el navegador sin ninguna configuración.

Preguntas frecuentes

¿Cómo convierto CSV a JSON en JavaScript sin una librería?

Divide el string CSV por salto de línea para obtener las filas, extrae las cabeceras de la primera fila con split(","), luego mapea las filas restantes a objetos con esas cabeceras como claves. Finaliza con JSON.stringify(array, null, 2) para producir un string JSON formateado. Este enfoque funciona bien para archivos CSV simples donde controlas el formato y sabes que no hay campos entrecomillados ni comas embebidas. Para datos provenientes de exportaciones de hojas de cálculo o sistemas de terceros, los campos entrecomillados y los valores multilínea romperán un divisor simple — usa PapaParse o csv-parse en esos casos. Para archivos muy pequeños procesados en el navegador, este enfoque sin dependencias es ideal.

JavaScript
const csv = `nombre,email,departamento
Carlos García,cgarcia@nexuslabs.io,Ingeniería
Ana López,alopez@nexuslabs.io,Producto`

const lines = csv.trim().split('\n')
const headers = lines[0].split(',')
const rows = lines.slice(1).map(line => {
  const values = line.split(',')
  return Object.fromEntries(headers.map((h, i) => [h.trim(), values[i]?.trim()]))
})

console.log(JSON.stringify(rows, null, 2))
// [
//   { "nombre": "Carlos García", "email": "cgarcia@nexuslabs.io", "departamento": "Ingeniería" },
//   { "nombre": "Ana López", "email": "alopez@nexuslabs.io", "departamento": "Producto" }
// ]

¿Cuál es la diferencia entre JSON.stringify() y toString() para objetos?

toString() sobre un objeto plano devuelve el string inútil "[object Object]" — no dice nada sobre los datos reales. JSON.stringify() produce un string JSON válido con todas las claves y valores correctamente serializados, incluyendo objetos y arrays anidados. Usa siempre JSON.stringify() cuando necesites convertir un objeto JavaScript (como una fila derivada de CSV) a un string JSON. Para revertir la operación — reconstruir objetos JavaScript vivos desde un string JSON — usa JSON.parse(), que es el inverso exacto de JSON.stringify(). Estas dos funciones forman un ciclo completo: stringify para persistir o transmitir datos, parse para consumirlos.

JavaScript
const fila = { nombre: 'Carlos García', rol: 'Ingeniero' }

console.log(fila.toString())         // "[object Object]"
console.log(JSON.stringify(fila))    // '{"nombre":"Carlos García","rol":"Ingeniero"}'

¿Cómo manejo campos CSV que contienen comas o comillas?

RFC 4180 especifica que los campos que contienen comas, saltos de línea o comillas dobles deben estar envueltos en comillas dobles, con las comillas embebidas escapadas duplicándolas ("" dentro de un campo entrecomillado representa una sola comilla literal). Un split(",") manual falla con estos archivos — el límite del campo ya no es una coma simple. Usa PapaParse o csv-parse para datos de producción, o escribe un analizador de máquina de estados que rastree si estás dentro de un campo entrecomillado. Escribir una máquina de estados correcta desde cero es sorprendentemente complicado: debes manejar comillas al inicio de un campo, comillas escapadas en medio del campo, saltos de línea dentro de campos entrecomillados y distintas convenciones de fin de línea (CRLF vs LF). Para cualquier cosa más allá de CSV de prueba, usa una librería bien probada.

JavaScript
// PapaParse maneja RFC 4180 correctamente
import Papa from 'papaparse'

const csv = `producto,descripcion,precio
"Widget, Grande","Un ""widget"" de primera calidad",29.99
Tornillo,Tornillo estándar M8,1.50`

const { data } = Papa.parse(csv, { header: true })
console.log(JSON.stringify(data, null, 2))
// el campo descripcion contiene correctamente: Un "widget" de primera calidad

¿Puedo convertir CSV a JSON directamente en el navegador?

Sí. Tanto JSON.stringify() como JSON.parse() están integrados en todos los motores de navegadores. Para el paso de análisis del CSV, puedes dividir por salto de línea y coma para archivos simples, o incluir PapaParse vía CDN (no tiene dependencias de Node.js). La conversión completa ocurre del lado del cliente sin viaje al servidor, lo que es útil para la privacidad de los archivos — los datos CSV nunca salen de la máquina del usuario. Cuando un usuario sube un archivo CSV mediante un elemento <input type="file">, el método file.text() de la File API devuelve una Promise que se resuelve con el contenido del archivo como string, que puedes pasar a tu función de conversión. Esto hace que la conversión de CSV a JSON completamente en el navegador sea práctica para paneles de control, herramientas para desarrolladores y cualquier aplicación que necesite procesar datos subidos sin un backend.

JavaScript
// Navegador: el usuario sube un archivo CSV
const fileInput = document.querySelector('input[type="file"]')
fileInput.addEventListener('change', async (event) => {
  const file = event.target.files[0]
  const text = await file.text()
  const lines = text.trim().split('\n')
  const headers = lines[0].split(',')
  const rows = lines.slice(1).map(line =>
    Object.fromEntries(headers.map((h, i) => [h.trim(), line.split(',')[i]?.trim()]))
  )
  console.log(JSON.stringify(rows, null, 2))
})

¿Cómo analizo valores numéricos y booleanos desde CSV en lugar de mantenerlos como strings?

CSV no tiene sistema de tipos — cada campo es un string. Debes convertir los valores durante el paso de mapeo. Comprueba patrones numéricos con Number() o parseFloat(), convierte "true"/"false" a booleanos y maneja los strings vacíos como null. Ten cuidado con los campos que parecen números pero deben mantenerse como strings: los códigos postales como "28001" pierden sus ceros iniciales al convertirse con Number(), y los números de teléfono o códigos de producto con caracteres numéricos son igualmente frágiles. La librería csvtojson realiza la conversión automática de tipos vía su opción colParser, que te permite especificar funciones de conversión por columna y anular la detección automática para columnas problemáticas. La opción dynamicTyping de PapaParse aplica la misma conversión de forma global en todas las columnas.

JavaScript
function convertirValor(val) {
  if (val === '') return null
  if (val === 'true') return true
  if (val === 'false') return false
  const num = Number(val)
  if (!isNaN(num) && val.trim() !== '') return num
  return val
}

// Aplicar durante el mapeo de CSV a objeto
const fila = { port: convertirValor('8443'), debug: convertirValor('true'), host: convertirValor('api.internal') }
// { port: 8443, debug: true, host: "api.internal" }

¿Cómo escribo la salida de CSV a JSON en un archivo en Node.js?

Usa fs.writeFileSync() con la salida de JSON.stringify(). El tercer argumento de JSON.stringify controla la indentación — pasa 2 para sangría de dos espacios o "\t" para tabulaciones. Para leer el archivo de vuelta, usa JSON.parse(fs.readFileSync(path, "utf8")), que reconstruye el array JavaScript vivo de objetos. Si escribes el archivo en un contexto asíncrono (dentro de una función async o en el nivel superior de un módulo ES), prefiere fs.promises.writeFile() para evitar bloquear el event loop mientras se completa la escritura. Para salidas JSON grandes de varios megabytes, considera canalizar un stream JSON a un WriteStream en lugar de construir el string completo en memoria antes de escribir.

JavaScript
import { writeFileSync, readFileSync } from 'node:fs'

// Escribir
const jsonOutput = JSON.stringify(rows, null, 2)
writeFileSync('empleados.json', jsonOutput, 'utf8')

// Leer de vuelta
const parsed = JSON.parse(readFileSync('empleados.json', 'utf8'))
console.log(Array.isArray(parsed))  // true
console.log(parsed[0].nombre)       // "Carlos García"

Herramientas relacionadas

También disponible en:Python
AC
Alex ChenFront-end & Node.js Developer

Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.

SL
Sophie LaurentRevisor técnico

Sophie is a full-stack developer focused on TypeScript across the entire stack — from React frontends to Express and Fastify backends. She has a particular interest in type-safe API design, runtime validation, and the patterns that make large JavaScript codebases stay manageable. She writes about TypeScript idioms, Node.js internals, and the ever-evolving JavaScript module ecosystem.