CSV to JSON JavaScript — Converter + Code Examples
Используйте бесплатный CSV to JSON прямо в браузере — установка не требуется.
Попробовать CSV to JSON онлайн →Большинство CSV-данных, с которыми приходится работать, поступает в виде плоской строки из загруженного файла, экспорта базы данных или API, который до сих пор использует формат из 70-х годов. Чтобы конвертировать CSV в JSON в JavaScript, вам нужны две вещи, которые язык предоставляет бесплатно: разбиение строк для парсинга строк и JSON.stringify() для сериализации результата. Для базовых задач npm-пакеты не нужны — это руководство охватывает полный пайплайн от переиспользуемой утилиты csvToJson() до PapaParse и файлового ввода-вывода в Node.js. Для быстрых конвертаций без написания кода онлайн-конвертер CSV в JSON справляется мгновенно. Все примеры рассчитаны на Node.js 18+ и современные браузеры.
- ✓Разбейте CSV по переносу строки, извлеките заголовки из строки 0, преобразуйте оставшиеся строки в объекты, затем JSON.stringify(array, null, 2) для форматированного вывода.
- ✓JSON.stringify() возвращает строку; JSON.parse() преобразует её обратно в живой JavaScript-массив — важно знать, с чем вы работаете, прежде чем выполнять операции.
- ✓Экземпляры Map не сериализуются в JSON автоматически — сначала вызовите Object.fromEntries(map).
- ✓Для CSV с экранированными полями, запятыми внутри значений или переносами строк в ячейках используйте PapaParse или csv-parse вместо ручного разбиения.
- ✓csvtojson (npm) обрабатывает приведение типов, стриминг и граничные случаи RFC 4180 в одном вызове.
Что такое конвертация CSV в JSON?
Конвертация CSV в JSON преобразует плоский текстовый формат с разделителями-запятыми в структурированный массив объектов, где каждая строка становится JavaScript-объектом с ключами из заголовков столбцов. Формат CSV не имеет типов данных — всё является строкой. JSON добавляет структуру, вложенность и явные типы (числа, булевы, null). Эта конвертация является первым шагом практически в каждом пайплайне обработки данных, который начинается с экспорта таблицы, выгрузки из устаревшей системы или загруженного пользователем файла. Сами данные остаются теми же; формат меняется от столбцов на основе позиций к именованным свойствам.
name,email,role,active Алексей Иванов,ivanov@nexuslabs.io,Engineering Lead,true Мария Соколова,sokolova@nexuslabs.io,Product Manager,true
[
{
"name": "Алексей Иванов",
"email": "ivanov@nexuslabs.io",
"role": "Engineering Lead",
"active": "true"
},
{
"name": "Мария Соколова",
"email": "sokolova@nexuslabs.io",
"role": "Product Manager",
"active": "true"
}
]csvToJson() — создание переиспользуемой функции конвертации
Полный пайплайн CSV-в-JSON в JavaScript состоит из трёх шагов: разбить CSV-строку по переносу строки для получения строк, извлечь заголовки из первой строки с помощью split(','), затем преобразовать каждую оставшуюся строку в обычный JavaScript-объект, где ключи берутся из заголовков, а значения — из соответствующих позиций столбцов. Финальный вызов JSON.stringify() преобразует этот массив объектов в JSON-строку. Вот минимальная рабочая версия:
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 = `server,port,region,status
api-gateway,8080,us-east-1,healthy
auth-service,8443,eu-west-1,degraded
payments-api,9090,ap-south-1,healthy`
console.log(csvToJson(csv))
// [
// { "server": "api-gateway", "port": "8080", "region": "us-east-1", "status": "healthy" },
// { "server": "auth-service", "port": "8443", "region": "eu-west-1", "status": "degraded" },
// { "server": "payments-api", "port": "9090", "region": "ap-south-1", "status": "healthy" }
// ]Эта функция обрабатывает базовые случаи: завершающие переносы строк, пустые строки, пробелы вокруг значений. Каждое поле CSV приходит как строка. Обратите внимание, что port является "8080" (строкой), а не 8080 (числом). Если вам нужны правильные типы в JSON-выводе, необходимо приводить их самостоятельно. Вот расширенная версия с определением типов:
function coerceValue(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 ? coerceValue(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 }
// ]Флаг coerce является опциональным, поскольку автоматическое определение типов может дать сбой — поле вроде почтового индекса ("07302") теряет ведущий ноль при преобразовании в число. Держите приведение отключённым по умолчанию и включайте только тогда, когда вы контролируете схему. Небольшое замечание: JSON.stringify() принимает третий аргумент space для отступа. Передайте 2 для двух пробелов, 4 для четырёх, или "\t" для табуляции. Опустите полностью для компактного однострочного вывода — это удобно при отправке JSON-строки как тела API-запроса, где пробелы лишь занимают полосу пропускания.
JSON.parse() напрямую на CSV-тексте выбрасывает SyntaxError. Сначала необходимо преобразовать CSV в JavaScript-объекты с помощью вашей функции csvToJson(), которая внутри вызывает JSON.stringify() для создания настоящей JSON-строки.Работа с Map, датами и пользовательскими объектами из CSV-данных
Не каждая конвертация CSV заканчивается плоским массивом обычных объектов. Иногда нужно построить Map из пар заголовок-значение, разобрать строки дат в объекты Date или добавить вычисляемые свойства перед сериализацией. У JavaScript есть особенность, которая часто сбивает с толку: экземпляры Map не сериализуются с помощью JSON.stringify(). Вы получаете пустой объект. Решение — Object.fromEntries() для преобразования Map обратно в обычный объект перед сериализацией.
Причина, по которой Map сериализуется в {}, заключается в том, что JSON.stringify() перебирает собственные перечисляемые свойства объекта. Map хранит свои записи во внутреннем слоте, а не как перечисляемые свойства самого объекта, поэтому сериализатор видит объект без ключей. У прототипа Map также нет метода toJSON(), который JSON.stringify() вызывает первым для любого значения перед принятием решения о способе сериализации. Если у значения есть toJSON(), возвращаемое этим методом значение и будет сериализовано — а не сам объект. Именно поэтому объекты Date сериализуются корректно: Date.prototype.toJSON возвращает строку ISO 8601, поэтому JSON.stringify(new Date()) создаёт строку-временную метку, а не пустой объект. Понимание этого механизма позволяет определить то же поведение в собственных классах — как показано в примере с EmployeeRecord ниже — чтобы контролировать, какие поля из CSV попадут в итоговый JSON-вывод.
Преобразование Map в JSON
// Строим Map из пар заголовок→значение CSV
const headers = ['server', 'port', 'region']
const values = ['payments-api', '9090', 'ap-south-1']
const rowMap = new Map(headers.map((h, i) => [h, values[i]]))
// Map НЕ сериализуется напрямую
console.log(JSON.stringify(rowMap))
// "{}" — пустой объект, данные потеряны!
// Сначала преобразуем в обычный объект
const rowObj = Object.fromEntries(rowMap)
console.log(JSON.stringify(rowObj, null, 2))
// {
// "server": "payments-api",
// "port": "9090",
// "region": "ap-south-1"
// }Строки дат и toJSON()
Поля дат из CSV приходят как строки. Если вы разбираете их в объекты Date при обработке, эти даты сериализуются корректно, поскольку у Date есть встроенный метод toJSON(), возвращающий строку ISO 8601. Вы также можете определить пользовательский toJSON() в собственных классах для управления тем, какие столбцы CSV появятся в сериализованном выводе — например, чтобы исключить внутренние поля отслеживания, такие как _rowIndex.
class EmployeeRecord {
constructor(csvRow) {
this._rowIndex = csvRow._rowIndex // внутреннее, не для JSON
this.employeeId = csvRow.employee_id
this.name = csvRow.name
this.hiredAt = new Date(csvRow.hired_date)
this.salary = Number(csvRow.salary)
}
toJSON() {
// Экспортируем только нужные поля
return {
employee_id: this.employeeId,
name: this.name,
hired_at: this.hiredAt, // Date.toJSON() → ISO-строка автоматически
salary: this.salary,
}
}
}
const csvRow = {
_rowIndex: 42,
employee_id: 'EMP-2847',
name: 'Алексей Иванов',
hired_date: '2024-03-15',
salary: '128000',
}
const record = new EmployeeRecord(csvRow)
console.log(JSON.stringify(record, null, 2))
// {
// "employee_id": "EMP-2847",
// "name": "Алексей Иванов",
// "hired_at": "2024-03-15T00:00:00.000Z",
// "salary": 128000
// }
// Примечание: _rowIndex исключён, salary — число, дата в формате ISOReviver для десериализации дат
После записи JSON из CSV в файл или отправки по сети JSON.parse() вернёт обычные объекты — объекты Date снова станут строками. Используйте функцию reviver для преобразования строк ISO 8601 обратно в объекты Date при разборе:
const jsonString = '{"employee_id":"EMP-2847","name":"Алексей Иванов","hired_at":"2024-03-15T00:00:00.000Z","salary":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.hired_at instanceof Date) // true
console.log(parsed.hired_at.getFullYear()) // 2024JSON.stringify() возвращает undefined (а не строку) для значений, содержащих функции, символы (Symbol) или свойства со значением undefined. Если ваши объекты из CSV случайно получают значение undefined из отсутствующего столбца, это свойство молча исчезнет из JSON-вывода. Всегда используйте null вместо отсутствующих значений.Справочник параметров JSON.stringify()
Сигнатура функции: JSON.stringify(value, replacer?, space?). Аргументы replacer и space оба необязательны — передайте null для replacer, когда вам нужен только отступ.
Параметры JSON.parse():
JSON.parse() — потребление JSON-вывода
Получив JSON-строку из csvToJson(), следующим шагом обычно является разбор её обратно в живой JavaScript-массив для фильтрации, преобразования или передачи в API. Важно различать JSON-строку (typeof === "string") и JavaScript-объект. Вы не можете вызвать .filter() или обратиться к [0].name на строке — сначала нужен JSON.parse(). Этот цикл (stringify затем parse) также работает как техника валидации: если конвертация CSV создала что-то, что не является валидным JSON, parse выбросит исключение. Необязательный аргумент reviver позволяет трансформировать каждую пару ключ-значение при разборе — полезно для восстановления объектов Date из ISO-строк или переименования ключей без отдельного прохода.
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`
// Шаг 1: конвертируем CSV в JSON-строку
const jsonString = csvToJson(csv, { coerce: true })
// Шаг 2: разбираем обратно в JavaScript-массив
const endpoints = JSON.parse(jsonString)
// Проверяем, что это массив
console.log(Array.isArray(endpoints)) // true
// Фильтруем медленные эндпоинты
const slow = endpoints.filter(ep => ep.avg_latency_ms > 200)
console.log(slow.map(ep => ep.endpoint))
// ["/api/v2/orders", "/api/v2/payments"]
// Деструктурируем первую строку
const [first, ...rest] = endpoints
console.log(first.endpoint) // "/api/v2/orders"
console.log(rest.length) // 3Безопасная обёртка для JSON.parse() полезна при валидации результата конвертации перед дальнейшей обработкой. Если конвертация CSV по какой-то причине создала некорректный JSON (усечённый ввод, ошибки кодировки), это перехватит ошибку без сбоя программы:
function safeParse(jsonString) {
try {
return { data: JSON.parse(jsonString), error: null }
} catch (err) {
return { data: null, error: err.message }
}
}
// Валидный вывод
const result = safeParse(csvToJson(csv))
if (result.error) {
console.error('CSV-конвертация создала невалидный JSON:', result.error)
} else {
console.log(`Разобрано ${result.data.length} строк`)
}
// Случайная передача сырого CSV в JSON.parse — это упадёт
const bad = safeParse('name,email\nАлексей,ivanov@nexuslabs.io')
console.log(bad.error) // "Unexpected token 'a', "name,email"... is not valid JSON"Reviver для переименования ключей и валидации
Функция reviver получает каждую пару ключ-значение при разборе, от внутренних свойств наружу. Возврат undefined для ключа полностью удаляет его из результата; возврат другого значения заменяет его. Reviver полезен для переименования заголовков (camelCase в snake_case), удаления внутренних полей или проверки наличия обязательных столбцов. Он вызывается с корневым значением последним (с ключом в виде пустой строки), где следует выбрасывать исключение, если результат не является массивом.
const jsonString = csvToJson(`employeeId,firstName,hiredDate
EMP-2847,Алексей,2024-03-15
EMP-3012,Мария,2023-11-01`, { coerce: false })
const camelToSnake = str => str.replace(/[A-Z]/g, c => '_' + c.toLowerCase())
const employees = JSON.parse(jsonString, function(key, value) {
// Корневое значение — валидируем форму
if (key === '') {
if (!Array.isArray(value)) throw new Error('Expected JSON array from CSV')
return value
}
// Переименовываем ключи camelCase в 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(employees[0])
// { employee_id: 'EMP-2847', first_name: 'Алексей', hired_date: '2024-03-15' }Конвертация CSV из файла и ответа API
Два реальных источника CSV-данных в продакшне: файлы на диске и HTTP-ответы. Оба сценария требуют обработки ошибок, поскольку ввод является внешним и неконтролируемым.
Читаем CSV-файл, конвертируем, записываем JSON
import { readFileSync, writeFileSync } from 'node:fs'
function csvToJsonFromFile(inputPath, outputPath) {
let csvText
try {
csvText = readFileSync(inputPath, 'utf8')
} catch (err) {
throw new Error(`Failed to read ${inputPath}: ${err.message}`)
}
const lines = csvText.trim().split('\n')
if (lines.length < 2) {
throw new Error(`${inputPath} has no data rows (only ${lines.length} line)`)
}
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(`Конвертировано ${rows.length} строк → ${outputPath}`)
return rows
}
// Использование
const data = csvToJsonFromFile('inventory.csv', 'inventory.json')
console.log(data[0])
// { sku: "WDG-2847", warehouse: "us-east-1", quantity: "150", ... }Получение CSV из API-эндпоинта
async function fetchCsvAsJson(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(`Unexpected content-type: ${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
}
// Пример: получаем CSV с курсами валют от поставщика данных
try {
const rates = await fetchCsvAsJson('https://data.ecb.internal/rates/daily.csv')
console.log(JSON.stringify(rates.slice(0, 3), null, 2))
// Отправляем как JSON в нижестоящий сервис
await fetch('https://api.internal/v2/rates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: rates }),
})
} catch (err) {
console.error('Rate sync failed:', err.message)
}JSON.stringify() позволяет задать белый список конкретных столбцов из CSV. Передайте массив имён заголовков, чтобы включить только эти поля: JSON.stringify(rows, ['name', 'email', 'department']). Свойства, не указанные в массиве, молча исключаются из вывода.Конвертация CSV в JSON из командной строки
Node.js умеет запускать встроенные скрипты, а для конвертации CSV в JSON без написания скрипта существуют специализированные CLI-инструменты.
# Передаём CSV во встроенный скрипт Node.js
cat servers.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 — швейцарский нож для структурированных данных # Установка: brew install miller (macOS) или apt install miller (Debian/Ubuntu) mlr --icsv --ojson cat inventory.csv # Фильтрация строк при конвертации mlr --icsv --ojson filter '$quantity > 100' inventory.csv # Выбор конкретных столбцов mlr --icsv --ojson cut -f sku,warehouse,quantity inventory.csv
# Глобальная установка npm install -g csvtojson # Конвертация файла csvtojson servers.csv > servers.json # Передача из stdin cat exports/q1-metrics.csv | csvtojson > q1-metrics.json
Для больших файлов Miller обычно предпочтительнее csvtojson. Miller написан на C и обрабатывает CSV как поток без загрузки всего файла в память, что позволяет обрабатывать многогигабайтные экспорты при постоянном потреблении памяти. Он также поддерживает операции на уровне полей — переименование столбцов, приведение типов, фильтрацию строк — до того, как данные станут JSON, что позволяет избежать двухэтапного пайплайна разбор-затем-преобразование. csvtojson, с другой стороны, работает в Node.js и удобнее, когда весь ваш инструментарий — это JavaScript: вы можете передавать его вывод напрямую в Node-стримы, импортировать как библиотеку или использовать его colParser API для приведения типов по столбцам в коде. Предпочитайте Miller для максимальной пропускной способности и конвейеров оболочки; предпочитайте csvtojson, когда нужна тесная интеграция с Node.js-приложением.
jq не умеет разбирать CSV нативно. Если вам нужен jq в пайплайне, сначала конвертируйте в JSON с помощью csvtojson или mlr, затем передайте JSON-вывод в jq для фильтрации и преобразования.Высокопроизводительная альтернатива — PapaParse
Ручной подход с split(',') не работает на реальных CSV-файлах. Экранированные поля с запятыми, встроенные переносы строк, экранированные двойные кавычки — всё это ломает наивный разделитель. PapaParse — это библиотека, к которой я обращаюсь, когда CSV поступает из неизвестного источника. Она обрабатывает все граничные случаи RFC 4180, автоматически определяет разделители и работает как в Node.js, так и в браузерах.
npm install papaparse
import Papa from 'papaparse'
const csv = `product,description,price,in_stock
"Widget, Large","A premium widget with ""extra"" features",29.99,true
Bolt Assembly,Standard M8 bolt kit,4.50,true
"Gasket Set","Includes gasket, seal, and O-ring",12.75,false`
const { data, errors, meta } = Papa.parse(csv, {
header: true,
dynamicTyping: true, // автоматически конвертирует числа и булевы
skipEmptyLines: true,
transformHeader: h => h.trim().toLowerCase().replace(/\s+/g, '_'),
})
if (errors.length > 0) {
console.error('Parse errors:', errors)
}
console.log(JSON.stringify(data, null, 2))
// [
// {
// "product": "Widget, Large",
// "description": "A premium widget with \"extra\" features",
// "price": 29.99,
// "in_stock": true
// },
// ...
// ]
console.log(`Разобрано ${data.length} строк, разделитель: "${meta.delimiter}"`)Опция dynamicTyping в PapaParse выполняет приведение типов автоматически — числа становятся числами, "true"/"false" — булевыми значениями. Коллбэк transformHeader нормализует имена столбцов в snake_case, избавляя вас от работы с непоследовательными заголовками из разных CSV-экспортов. Для быстрых конвертаций без написания кода конвертер CSV в JSON обрабатывает всё это в браузере.
Вывод в терминале с подсветкой синтаксиса
Большой JSON-массив в терминале очень трудно читать. Подсветка синтаксиса делает вывод читаемым при отладке и разработке. Пакет cli-highlight раскрашивает JSON-вывод в Node.js-терминалах.
npm install cli-highlight
import { highlight } from 'cli-highlight'
// После конвертации CSV в JSON-массив
const jsonOutput = JSON.stringify(rows, null, 2)
// Печатаем с подсветкой синтаксиса
console.log(highlight(jsonOutput, { language: 'json' }))
// Ключи, строки, числа и булевы значения получают разные цветаЦветной вывод особенно полезен при интерактивной проверке большого результата конвертации. JSON-ключи, строковые значения, числа и булевы значения получают разные ANSI-цвета, что упрощает обнаружение поля с неправильным типом — например, номер порта, который должен быть 8080, но подсвечивается как строка, потому что приведение типов было отключено. Это особенно полезно при отладке CSV-файлов, экспортированных из табличных инструментов, где типы столбцов непоследовательны по строкам. Без цвета просмотр 50 строк JSON в поисках одного поля с неправильным типом требует чтения каждого значения по отдельности. С цветом строка-число сразу бросается в глаза.
Работа с большими CSV-файлами
Загрузка CSV-файла размером 500 МБ в строку с помощью readFileSync() займёт много памяти и может привести к сбою процесса. Для больших файлов обрабатывайте CSV построчно и выдавайте JSON-объекты по мере поступления. Пакет csv-parse (часть экосистемы csv на npm) предоставляет потоковый парсер, работающий с Node.js-стримами.
Стриминг CSV в NDJSON с csv-parse
NDJSON (Newline-Delimited JSON) — формат, где каждая строка выходного файла является самостоятельным JSON-объектом. В отличие от единого большого JSON-массива — который требует нахождения всего файла в памяти, прежде чем начать его читать — файлы NDJSON можно обрабатывать построчно. Это делает NDJSON идеальным для больших наборов данных, которые будут потребляться обработчиками логов, конвейерами стримов или базами данных с API массовой загрузки. Пакет csv-parse выдаёт один JavaScript-объект на строку CSV в объектном режиме, поэтому его можно напрямую передавать в transform-стрим, который добавляет \n после каждого 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'
// Преобразуем каждый объект-строку CSV в JSON-строку
const toNdjson = new Transform({
objectMode: true,
transform(record, encoding, callback) {
callback(null, JSON.stringify(record) + '\n')
},
})
await pipeline(
createReadStream('telemetry-2026-03.csv'),
parse({
columns: true, // используем первую строку как заголовки
skip_empty_lines: true,
trim: true,
cast: true, // автоматически конвертируем числа и булевы
}),
toNdjson,
createWriteStream('telemetry-2026-03.ndjson')
)
console.log('Потоковая конвертация завершена')
// Каждая строка выходного файла — один 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}
// ...Потоковый режим PapaParse для браузера и Node.js
Потоковый режим PapaParse использует коллбэк step, который вызывается один раз на строку вместо сбора всех строк в память. Вы передаёте ему Node.js ReadStream (в Node.js) или объект File (в браузере), а PapaParse обрабатывает разбиение на части внутри. Никакого стрим-пайплайна настраивать не нужно — только коллбэк. Используйте его, когда нужна совместимость с RFC 4180 без подключения csv-parse.
import Papa from 'papaparse'
import { createReadStream } from 'node:fs'
let rowCount = 0
const errors = []
const fileStream = createReadStream('warehouse-inventory.csv')
Papa.parse(fileStream, {
header: true,
dynamicTyping: true,
step(result) {
// Обрабатываем по одной строке — постоянное потребление памяти
rowCount++
if (result.data.quantity === 0) {
errors.push(`Строка ${rowCount}: ${result.data.sku} закончилась`)
}
},
complete() {
console.log(`Обработано ${rowCount} строк`)
if (errors.length > 0) {
console.log(`Обнаружено проблем: ${errors.length}`)
errors.forEach(e => console.log(` ${e}`))
}
},
error(err) {
console.error('Parse failed:', err.message)
},
})Распространённые ошибки
Проблема: CSV — это не JSON. Передача сырой CSV-строки в JSON.parse() выбрасывает SyntaxError, поскольку запятые и переносы строк не являются допустимым синтаксисом JSON.
Решение: Сначала разберите CSV в JavaScript-объекты с помощью split() или библиотеки, затем используйте JSON.stringify() для создания JSON. Вызывайте JSON.parse() только на строках, которые уже являются валидным JSON.
const csv = 'name,email\nАлексей Иванов,ivanov@nexuslabs.io' const data = JSON.parse(csv) // SyntaxError: Unexpected token 'a'
const csv = 'name,email\nАлексей Иванов,ivanov@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) // валидная JSON-строкаПроблема: Вызов toString() на JavaScript-объекте возвращает бесполезную строку '[object Object]' вместо реальных данных. Это молча уничтожает результат конвертации CSV в JSON.
Решение: Всегда используйте JSON.stringify() для преобразования JavaScript-объектов в JSON-строки. toString() предназначен для приведения примитивов к строкам, а не для сериализации.
const row = { server: 'api-gateway', port: 8080 }
const output = row.toString()
// "[object Object]" — данные потеряныconst row = { server: 'api-gateway', port: 8080 }
const output = JSON.stringify(row, null, 2)
// '{"server":"api-gateway","port":8080}'Проблема: Наивный split(",") ломается, когда значения CSV содержат запятые внутри экранированных полей: "Widget, Large" превращается в два отдельных значения вместо одного.
Решение: Используйте PapaParse или csv-parse для любых CSV-данных, которые вы не полностью контролируете. Если необходимо разбирать вручную, реализуйте конечный автомат-парсер, отслеживающий, находится ли текущая позиция внутри экранированного поля.
const line = '"Widget, Large","Premium quality",29.99'
const values = line.split(',')
// ["\"Widget", " Large\"", "\"Premium quality\"", "29.99"]
// 4 значения вместо 3 — первое поле разбито неверноimport Papa from 'papaparse'
const { data } = Papa.parse('"Widget, Large","Premium quality",29.99')
// data[0] = ["Widget, Large", "Premium quality", "29.99"]
// 3 значения, корректно разобраноПроблема: Без приведения типов port: "8080" остаётся строкой в JSON вместо числа. Нижестоящие системы, ожидающие числовые типы, отклоняют или неправильно обрабатывают данные.
Решение: Применяйте явное приведение типов на этапе преобразования или используйте PapaParse с dynamicTyping: true. Всегда осознанно подходите к тому, какие поля должны быть числовыми.
const row = { port: '8443', debug: 'true', workers: '4' }
JSON.stringify(row)
// {"port":"8443","debug":"true","workers":"4"} — всё строкиconst row = {
port: Number('8443'), // 8443
debug: 'true' === 'true', // true
workers: Number('4'), // 4
}
JSON.stringify(row)
// {"port":8443,"debug":true,"workers":4} — правильные типыРучной разбор vs библиотеки — быстрое сравнение
Для быстрых скриптов, где вы контролируете формат CSV и знаете, что нет экранированных полей, встроенный подход с split() + JSON.stringify() работает и не требует никаких зависимостей. Для производственных систем, обрабатывающих загруженные пользователями CSV-файлы, используйте PapaParse в браузере или csv-parse в Node.js — оба корректно обрабатывают RFC 4180 и поддерживают стриминг. Пакет csvtojson — единственный, который напрямую выдаёт JSON, обрабатывая и разбор, и сериализацию в одном вызове. Когда нужен максимально быстрый путь от вставки до результата, конвертер CSV в JSON справляется со всем этим в браузере без какой-либо настройки.
Часто задаваемые вопросы
Как конвертировать CSV в JSON в JavaScript без библиотек?
Разбейте CSV-строку по символу переноса строки, чтобы получить строки, извлеките заголовки из первой строки с помощью split(","), затем преобразуйте оставшиеся строки в объекты с ключами из заголовков. В конце вызовите JSON.stringify(array, null, 2) для форматированного JSON. Этот подход хорошо работает для простых CSV-файлов, в которых вы контролируете формат и знаете, что нет экранированных полей или встроенных запятых. Для данных из экспорта таблиц или сторонних систем экранированные поля и многострочные значения сломают наивный разделитель — в таких случаях используйте PapaParse или csv-parse. Для небольших файлов, обрабатываемых в браузере, этот подход без зависимостей идеален.
const csv = `name,email,department
Алексей Иванов,ivanov@nexuslabs.io,Engineering
Мария Соколова,sokolova@nexuslabs.io,Product`
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))
// [
// { "name": "Алексей Иванов", "email": "ivanov@nexuslabs.io", "department": "Engineering" },
// { "name": "Мария Соколова", "email": "sokolova@nexuslabs.io", "department": "Product" }
// ]В чём разница между JSON.stringify() и toString() для объектов?
Вызов toString() на обычном объекте возвращает бесполезную строку "[object Object]" — она ничего не говорит о реальных данных. JSON.stringify() создаёт валидную JSON-строку со всеми ключами и значениями, правильно сериализованными, включая вложенные объекты и массивы. Всегда используйте JSON.stringify(), когда нужно преобразовать JavaScript-объект (например, строку из CSV) в JSON-строку. Для обратной операции — восстановления живых JavaScript-объектов из JSON-строки — используйте JSON.parse(), который является точной противоположностью JSON.stringify(). Эти две функции образуют полный цикл: stringify для сохранения или передачи данных, parse для их потребления.
const row = { name: 'Алексей Иванов', role: 'Engineer' }
console.log(row.toString()) // "[object Object]"
console.log(JSON.stringify(row)) // '{"name":"Алексей Иванов","role":"Engineer"}'Как обрабатывать поля CSV, содержащие запятые или кавычки?
RFC 4180 указывает, что поля, содержащие запятые, переносы строк или двойные кавычки, должны быть заключены в двойные кавычки, а встроенные кавычки экранируются удвоением ("" внутри экранированного поля означает одну кавычку). Ручной split(",") ломается на таких файлах — граница поля больше не является простой запятой. Используйте PapaParse или csv-parse для производственных данных, или напишите конечный автомат-парсер, отслеживающий, находитесь ли вы внутри экранированного поля. Написать корректный конечный автомат с нуля на удивление сложно: нужно обрабатывать кавычки в начале поля, экранированные кавычки внутри поля, переносы строк внутри экранированных полей и разные соглашения о концах строк (CRLF vs LF). Для любых данных сложнее тестовых — используйте проверенную библиотеку.
// PapaParse корректно обрабатывает RFC 4180
import Papa from 'papaparse'
const csv = `product,description,price
"Widget, Large","A big ""widget""",29.99
Bolt,Standard bolt,1.50`
const { data } = Papa.parse(csv, { header: true })
console.log(JSON.stringify(data, null, 2))
// поле description корректно содержит: A big "widget"Можно ли конвертировать CSV в JSON прямо в браузере?
Да. Оба метода — JSON.stringify() и JSON.parse() — встроены в каждый браузерный движок. Для шага разбора CSV можно разбивать по переносу строк и запятым для простых файлов или подключить PapaParse через CDN (у него нет зависимостей от Node.js). Вся конвертация происходит на стороне клиента без обращения к серверу, что полезно для конфиденциальности файлов — сырые CSV-данные никогда не покидают машину пользователя. Когда пользователь загружает CSV-файл через элемент <input type="file">, метод file.text() из File API возвращает Promise, который разрешается в содержимое файла в виде строки, которую затем можно передать в функцию конвертации. Это делает полностью браузерную конвертацию CSV в JSON практичной для дашбордов, инструментов разработчика и любых приложений, обрабатывающих загруженные данные без бэкенда.
// Браузер: пользователь загружает 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))
})Как обрабатывать числовые и булевы значения из CSV вместо того, чтобы хранить всё как строки?
В CSV нет системы типов — каждое поле является строкой. Вы должны приводить значения к нужным типам на этапе преобразования. Проверяйте числовые шаблоны с помощью Number() или parseFloat(), конвертируйте "true"/"false" в булевы значения и обрабатывайте пустые строки как null. Будьте осторожны с полями, которые выглядят как числа, но должны оставаться строками: почтовые индексы вроде "07302" теряют ведущий ноль при приведении с помощью Number(), и телефонные номера или коды товаров с числовыми символами так же уязвимы. Библиотека csvtojson выполняет автоматическое приведение типов через опцию colParser, которая позволяет задавать функции преобразования для каждого столбца и переопределять автодетекцию для проблемных столбцов. Опция dynamicTyping в PapaParse применяет то же приведение глобально ко всем столбцам.
function coerceValue(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
}
// Применяется при преобразовании CSV в объекты
const row = { port: coerceValue('8443'), debug: coerceValue('true'), host: coerceValue('api.internal') }
// { port: 8443, debug: true, host: "api.internal" }Как записать результат CSV-в-JSON в файл в Node.js?
Используйте fs.writeFileSync() с результатом JSON.stringify(). Третий аргумент JSON.stringify управляет отступом — передайте 2 для двух пробелов или "\t" для табуляции. Чтобы прочитать файл обратно, используйте JSON.parse(fs.readFileSync(path, "utf8")), который восстанавливает живой JavaScript-массив объектов. Если вы записываете файл в асинхронном контексте (внутри async-функции или на верхнем уровне ES-модуля), предпочтительнее fs.promises.writeFile() — чтобы не блокировать цикл событий во время записи. Для больших JSON-файлов объёмом несколько мегабайт рассмотрите передачу JSON-потока в WriteStream вместо построения всей строки в памяти перед записью.
import { writeFileSync, readFileSync } from 'node:fs'
// Запись
const jsonOutput = JSON.stringify(rows, null, 2)
writeFileSync('employees.json', jsonOutput, 'utf8')
// Чтение обратно
const parsed = JSON.parse(readFileSync('employees.json', 'utf8'))
console.log(Array.isArray(parsed)) // true
console.log(parsed[0].name) // "Алексей Иванов"Связанные инструменты
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.