CSV to JSON JavaScript — Converter + Code Examples
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.
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:
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:
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.
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
// 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.
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 ISOReviver 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:
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()) // 2024JSON.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ámetros de JSON.parse():
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.
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) // 3Un 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:
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.
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
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
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)
}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.
# 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));
"# 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
# 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.
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.
npm install papaparse
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.
npm install cli-highlight
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 distintosLa 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.
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).
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.
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)
},
})Errores comunes
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.
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álidoProblema: 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.
const fila = { servidor: 'api-gateway', puerto: 8080 }
const output = fila.toString()
// "[object Object]" — los datos se han perdidoconst fila = { servidor: 'api-gateway', puerto: 8080 }
const output = JSON.stringify(fila, null, 2)
// '{"servidor":"api-gateway","puerto":8080}'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.
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 incorrectamenteimport Papa from 'papaparse'
const { data } = Papa.parse('"Widget, Grande","Calidad premium",29.99')
// data[0] = ["Widget, Grande", "Calidad premium", "29.99"]
// 3 valores, correctamente analizadosProblema: 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.
const fila = { puerto: '8443', debug: 'true', workers: '4' }
JSON.stringify(fila)
// {"puerto":"8443","debug":"true","workers":"4"} — todos stringsconst fila = {
puerto: Number('8443'), // 8443
debug: 'true' === 'true', // true
workers: Number('4'), // 4
}
JSON.stringify(fila)
// {"puerto":8443,"debug":true,"workers":4} — tipos correctosAnálisis manual vs librerías — comparación rápida
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.
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.
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.
// 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.
// 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.
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.
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
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.
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.