Руководство по конвертации CSV ↔ JSON: способы, ловушки и лучшие практики
Команда эксплуатации присылает вам CSV-экспорт. Ваш API ожидает JSON. Вы открываете файл, смотрите на 10 000 строк значений через запятую и думаете: какой самый быстрый способ конвертировать CSV в JSON, не теряя данные?
Это руководство охватывает четыре способа конвертации (браузерные инструменты, JavaScript, Python, CLI), обратное направление (JSON в CSV), пять ловушек, тихо портящих данные, и подход к файлам, не помещающимся в память.
CSV против JSON: когда какой формат
Перед конвертацией полезно понимать сильные стороны каждого формата.
| Параметр | CSV | JSON |
|---|---|---|
| Структура | Плоская таблица (строки и колонки) | Вложенная иерархия (объекты, массивы) |
| Типы данных | Всё — строки | string, number, boolean, null |
| Читаемость | Удобен в таблицах | Удобен разработчику |
| Применение | Экспорт/импорт данных, отчёты, ETL | API, конфиги, NoSQL-хранилище |
| Размер файла | Меньше (нет повторяющихся ключей) | Больше (ключи повторяются в каждой записи) |
| Схема | Неявная (строка заголовка) | Явная (или JSON Schema) |
Правило большого пальца: применяйте CSV, когда данные табличные, а потребитель — таблица или конвейер данных. Применяйте JSON, когда данные иерархичны или потребитель — API. Структуру JSON-вывода всегда можно проверить в форматировщике JSON, чтобы поймать структурные проблемы заранее.
Если ваш проект использует расширенные форматы JSON5 или JSONC для конфигурации, см. руководство по JSON5 и JSONC для синтаксических различий и инструментов.
Четыре способа конвертации CSV в JSON
Способ 1 — браузерный инструмент
Для разовых конвертаций браузерный подход — самый быстрый путь. Вставьте CSV в онлайн-конвертер, получите JSON, затем проверьте результат в форматировщике JSON, чтобы убедиться в корректности структуры.
Преимущество: данные никогда не покидают браузер. Никаких загрузок, никакой серверной обработки, никаких вопросов приватности. Это важно при работе с внутренними данными, API-ключами в экспортах или чем угодно, что вы не хотите отправлять стороннему серверу.
Лучше всего подходит для: небольших файлов (до 10 МБ), быстрых разовых конвертаций, нетехнических членов команды.
Способ 2 — JavaScript / Node.js
Браузер (vanilla JS):
function csvToJson(csv) {
const lines = csv.trim().split('\n');
const headers = lines[0].split(',').map(h => h.trim());
return lines.slice(1).map(line => {
const values = line.split(',');
return headers.reduce((obj, header, i) => {
obj[header] = values[i]?.trim() ?? '';
return obj;
}, {});
});
}
const csv = `name,age,city
Alice,30,New York
Bob,25,London`;
console.log(JSON.stringify(csvToJson(csv), null, 2));
Это работает для простого CSV без полей в кавычках. Для production с запятыми внутри значений, переносами строк в полях или строками в кавычках берите полноценный парсер.
Node.js (csv-parser + потоки):
import { createReadStream } from 'fs';
import { parse } from 'csv-parse';
const records = [];
createReadStream('data.csv')
.pipe(parse({ columns: true, trim: true, skip_empty_lines: true }))
.on('data', (row) => records.push(row))
.on('end', () => {
console.log(JSON.stringify(records, null, 2));
});
Опция columns: true берёт первую строку как ключи. Опция trim убирает пробелы из значений. Это корректно обрабатывает поля в кавычках, экранированные запятые и многострочные значения.
Способ 3 — Python
Стандартная библиотека (без зависимостей):
import csv
import json
with open('data.csv', encoding='utf-8') as f:
reader = csv.DictReader(f)
rows = list(reader)
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(rows, f, indent=2, ensure_ascii=False)
csv.DictReader отображает каждую строку в словарь, используя строку заголовка как ключи. Флаг ensure_ascii=False сохраняет Unicode-символы (китайские, японские, с диакритиками) вместо экранирования в \uXXXX.
Pandas (однострочник для data scientist):
import pandas as pd
df = pd.read_csv('data.csv')
df.to_json('data.json', orient='records', indent=2, force_ascii=False)
Когда что выбирать:
- csv + json: лёгкие скрипты, Lambda-функции, контейнеры, где нужны минимальные зависимости.
- pandas: когда нужно ещё и почистить, фильтровать или преобразовать данные перед конвертацией. Накладные расходы импорта pandas оправданы, если вы делаете больше, чем просто конвертация формата.
Способ 4 — CLI-инструменты
Для shell-скриптов и конвейеров автоматизации:
csvkit:
# Установка: pip install csvkit
csvjson data.csv > data.json
Miller (mlr):
# Установка: brew install miller (macOS) или apt install miller (Ubuntu)
mlr --csv --json cat data.csv > data.json
Конвейер с jq для фильтрации:
# Конвертация и фильтрация в одном пайплайне
csvjson data.csv | jq '[.[] | select(.age | tonumber > 25)]'
Miller особенно силён, потому что нативно работает с CSV, JSON, TSV и другими форматами. Можно преобразовать данные при конвертации:
# Конвертируем CSV в JSON, переименовываем поле, добавляем вычисляемое поле
mlr --csv --json rename name,fullName then put '$age_group = ($age > 30) ? "senior" : "junior"' data.csv
Подробнее о jq-паттернах для предобработки JSON см. в шпаргалке по jq.
JSON в CSV: обратное направление
Конвертация JSON в CSV приносит сложности, отсутствующие в прямом направлении.
Сплющивание вложенных объектов
CSV по природе плоский. Когда в JSON есть вложенные объекты, нужна стратегия сплющивания:
{
"name": "Alice",
"address": {
"city": "New York",
"zip": "10001"
}
}
Превращается в:
| name | address.city | address.zip |
|---|---|---|
| Alice | New York | 10001 |
Точечная нотация (address.city) — самый распространённый подход. На Python:
import pandas as pd
data = [
{"name": "Alice", "address": {"city": "New York", "zip": "10001"}},
{"name": "Bob", "address": {"city": "London", "zip": "EC1A"}}
]
df = pd.json_normalize(data)
df.to_csv('output.csv', index=False)
# Колонки: name, address.city, address.zip
Обработка массивов
Поля-массивы требуют решения:
| Стратегия | Пример входа | CSV-вывод | Лучший сценарий |
|---|---|---|---|
| Объединить в строку | ["admin","editor"] | admin;editor | Простые списки, обратимо |
| Развернуть в колонки | ["admin","editor"] | role_0: admin, role_1: editor | Массивы фиксированной длины |
| Развернуть в строки | ["admin","editor"] | Две строки, по одной на роль | Реляционный анализ |
Выбирайте по потребителю. Если CSV возвращается в БД, развёртывание в строки обычно оправданнее.
Потеря информации о типах
В CSV нет системы типов. При конвертации JSON в CSV:
trueстановится строкой"true"— это boolean или строка?nullстановится пустой ячейкой — не отличается от пустой строки""42становится"42"— это число или строка?
Если важна точность round-trip (CSV → обработка → JSON), задокументируйте свои конвенции типов в комментарии заголовка или сопутствующем файле схемы.
Пять распространённых ловушек и как их избежать
Эти проблемы тихо портят данные. Большинство туториалов их пропускает. Не учитесь на проблемах в продакшене.
1. Минное поле кодировок
Проблема: открываете CSV от коллеги и видите é вместо é или 锟斤拷 вместо китайских иероглифов.
Почему: файл сохранён в одной кодировке (Windows-1252, GBK, Shift_JIS), а парсер ожидает UTF-8. Excel под Windows часто сохраняет CSV в Windows-1252 или добавляет UTF-8 BOM (Byte Order Mark — невидимые \xEF\xBB\xBF в начале файла).
Решение:
# Сначала определим кодировку
import chardet
with open('data.csv', 'rb') as f:
result = chardet.detect(f.read(10000))
print(result) # {'encoding': 'Windows-1252', 'confidence': 0.73}
# Затем читаем с правильной кодировкой
with open('data.csv', encoding=result['encoding']) as f:
reader = csv.DictReader(f)
# ...
В Node.js явно срезаем BOM:
import { readFileSync } from 'fs';
let content = readFileSync('data.csv', 'utf-8');
// Срезаем UTF-8 BOM, если есть
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
2. Путаница с разделителем
Проблема: парсер выдаёт одну гигантскую колонку вместо нескольких полей.
Почему: во многих европейских локалях (Франция, Германия, Испания) Excel применяет точку с запятой (;) как разделитель CSV, потому что запятая — десятичный разделитель (например, 3,14 вместо 3.14). Tab-separated файлы (.tsv) добавляют ещё один вариант.
Решение: автоматически определять разделитель по первым строкам:
import csv
with open('data.csv') as f:
sample = f.read(8192)
dialect = csv.Sniffer().sniff(sample, delimiters=',;\t|')
f.seek(0)
reader = csv.DictReader(f, dialect=dialect)
3. Ведущие нули исчезают
Проблема: zip-код 00501 становится 501. Код товара 007 становится 7.
Почему: парсер (или Excel) интерпретирует поле как число и срезает ведущие нули. Особенно опасно для zip-кодов, телефонных номеров и кодов идентификаторов.
Решение: принудительно строковый тип. В pandas:
df = pd.read_csv('data.csv', dtype={'zip': str, 'product_code': str})
В JavaScript проверяем, отличается ли исходная строка от её числового представления:
function preserveLeadingZeros(value) {
if (/^0\d+$/.test(value)) return value; // Оставляем как строку
const num = Number(value);
return isNaN(num) ? value : num;
}
4. Потеря точности больших чисел
Проблема: ID 9007199254740993 становится 9007199254740992 в JSON.
Почему: JavaScript Number — это 64-битный float (IEEE 754). Целые выше Number.MAX_SAFE_INTEGER (2^53 − 1 = 9007199254740991) теряют точность. Это бьёт по ID в БД, Snowflake ID и Twitter/X-постам. Сравнение Snowflake с UUID v7 и другими ID-форматами разобрано в сравнении UUID v4, v7, ULID и Snowflake.
Решение: храните большие числа как строки в JSON или применяйте BigInt в коде обработки:
// Парсинг с сохранением строки для больших чисел
function safeParseNumber(value) {
const num = Number(value);
if (Number.isInteger(num) && !Number.isSafeInteger(num)) {
return value; // Оставляем как строку для сохранения точности
}
return isNaN(num) ? value : num;
}
5. Неоднозначность пустых значений
Проблема: в CSV пустые ячейки. После конвертации непонятно, было ли исходное значение пустой строкой "", null или просто отсутствовало.
Почему: в CSV нет способа различить эти три состояния. Пустое поле между двумя запятыми (Alice,,30) может означать любое из трёх.
Решение: задайте конвенцию и применяйте её последовательно:
def parse_value(value):
if value == '':
return None # или '' — выбор за вами
if value == 'NULL' or value == 'null':
return None
return value
Если данные используют sentinel-значения вроде NULL, N/A или -, документируйте и обрабатывайте их явно.
Стриминг больших файлов
Когда CSV превышает 100 МБ, загружать его целиком в память — не вариант. Применяйте стриминг.
Node.js (поток через pipeline):
import { createReadStream, createWriteStream } from 'fs';
import { parse } from 'csv-parse';
import { Transform } from 'stream';
import { pipeline } from 'stream/promises';
let first = true;
const toJsonArray = new Transform({
objectMode: true,
transform(record, encoding, callback) {
const prefix = first ? '[\n' : ',\n';
first = false;
callback(null, prefix + JSON.stringify(record));
},
flush(callback) {
callback(null, '\n]');
}
});
await pipeline(
createReadStream('large.csv'),
parse({ columns: true, trim: true }),
toJsonArray,
createWriteStream('large.json')
);
Python (генератор):
import csv
import json
def csv_rows(path):
with open(path, encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
yield row
# Стримим в JSON Lines (по одному JSON-объекту на строку)
with open('large.jsonl', 'w', encoding='utf-8') as out:
for row in csv_rows('large.csv'):
out.write(json.dumps(row, ensure_ascii=False) + '\n')
Для очень больших файлов рассмотрите формат JSON Lines (.jsonl) вместо одного JSON-массива. Каждая строка — независимый JSON-объект, и выходной файл тоже можно обрабатывать построчно — без разбора всего разом.
Часто задаваемые вопросы
В чём разница между CSV и JSON?
CSV (Comma-Separated Values) хранит данные как плоскую таблицу, где всякое значение — строка. JSON (JavaScript Object Notation) хранит структурированные данные с вложенными объектами, массивами и типизированными значениями (строки, числа, boolean, null). CSV меньше и удобен в таблицах; JSON выразительнее и удобен для API.
Как конвертировать CSV в JSON в JavaScript?
Применяйте пакет csv-parse в Node.js: читайте файл createReadStream, прокидывайте через parse({ columns: true }) и собирайте результаты. Для браузера читайте файл через FileReader, разбейте по переводам строк и отображайте строки в объекты, используя строку заголовка как ключи.
Как конвертировать CSV в JSON в Python?
Применяйте csv.DictReader из стандартной библиотеки, чтобы читать строки как словари, затем json.dump(), чтобы записать их как JSON-массив. Для манипуляций с данными перед конвертацией однострочник pandas.read_csv() плюс df.to_json(orient='records') — отличная альтернатива.
Можно ли конвертировать вложенный JSON в CSV?
Да, но нужна стратегия сплющивания. Самый распространённый подход — точечная нотация: поле вроде address.city становится заголовком колонки. На Python pandas.json_normalize() справляется автоматически. Массивы требуют дополнительных решений — объединить в строки, развернуть в колонки или развернуть в строки.
Почему мой CSV содержит «крякозябры» после конвертации?
Несовпадение кодировок. Файл, скорее всего, сохранён в Windows-1252 или GBK, а парсер ожидает UTF-8. Применяйте библиотеку определения вроде chardet (Python), затем явно укажите кодировку при чтении. Также проверьте на UTF-8 BOM, который некоторые инструменты добавляют автоматически.
Как обработать CSV-файл больше 100 МБ?
Применяйте стриминг вместо загрузки в память. В Node.js — потоки через csv-parse. В Python — итерация через csv.DictReader и генератор. Рассмотрите формат JSON Lines (.jsonl) вместо одного JSON-массива для удобства последующей обработки.
Как проверить, что сконвертированный JSON валиден?
Вставьте вывод в онлайн-форматировщик JSON, чтобы проверить синтаксис, структуру и вложенность. Для автоматической валидации применяйте JSON.parse() в JavaScript или json.loads() в Python — оба бросают понятные ошибки на невалидном вводе. Для валидации схемы определите JSON Schema и валидируйте программно.
Главные выводы
- Выбирайте подходящий способ под контекст: браузерные инструменты для разовых задач, код для автоматизации, CLI для конвейеров.
- Следите за проблемами кодировок — всегда явно указывайте кодировку, не полагайтесь на дефолты.
- Сохраняйте типы намеренно — ведущие нули, большие целые и null-значения требуют явной обработки.
- Стримьте большие файлы вместо загрузки в память. Рассмотрите JSON Lines для очень больших датасетов.
- Валидируйте вывод — прогоните сконвертированный JSON через форматировщик JSON, чтобы поймать структурные проблемы до продакшена.