← Вернуться к блогу

Как парсить большие JSON-файлы без сбоев в 2026 году

Полное руководство по работе с большими JSON-файлами (100MB+) в JavaScript и Python. Изучите потоковую передачу, прогрессивный парсинг и фрагментацию для предотвращения ошибок памяти.

Big JSON Team15 мин чтенияprogramming
B

Big JSON Team

Technical Writer

Expert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.

15 мин чтения

# Как парсить большие JSON-файлы без сбоев в 2026 году

Введение

Сталкивались ли вы когда-нибудь с ошибкой "FATAL ERROR: JavaScript heap out of memory" при попытке распарсить большой JSON-файл? Вы не одиноки. Обработка больших JSON-файлов (100MB+) — одна из самых распространённых проблем в современной веб-разработке.

В этом подробном руководстве вы изучите проверенные методы эффективного парсинга больших JSON-файлов без исчерпания памяти или краха приложения.

Почему JSON.parse() не работает с большими файлами

Стандартный метод JSON.parse() загружает весь файл в память сразу:

// ⚠️ Плохо для больших файлов (>100MB)

const fs = require('fs');

const data = fs.readFileSync('huge-file.json', 'utf8');

const json = JSON.parse(data); // Потребляет огромное количество памяти!

Почему это не работает:
  • Хранит всю строку в памяти (2-3× размер файла)
  • Создаёт полный объект JavaScript (ещё 2-5× размер файла)
  • Нет освобождения памяти до завершения
  • Итого: файл 500MB может использовать более 2GB памяти!

Решение 1: Потоковая передача с JSONStream (Node.js)

Лучший подход для больших JSON-файлов — потоковая передача — обработка данных по частям.

Практический пример

const fs = require('fs');

const JSONStream = require('JSONStream');

// Парсинг массива объектов

const stream = fs.createReadStream('large-dataset.json', { encoding: 'utf8' });

const parser = JSONStream.parse(''); // '' для элементов массива

let count = 0;

parser.on('data', (item) => {

// Обработка одного элемента за раз

console.log(Обработка записи ${++count}:, item.id);

// Ваша логика здесь - вставка в БД, преобразование и т.д.

if (item.price > 1000) {

saveToDatabase(item);

}

});

parser.on('end', () => {

console.log(Успешно обработано ${count} записей);

});

parser.on('error', (err) => {

console.error('Ошибка парсинга:', err);

});

stream.pipe(parser);

Парсинг вложенных JSON-путей

// Для: { "users": [ {...}, {...} ] }

const parser = JSONStream.parse('users.');

// Для: { "data": { "items": [...] } }

const parser = JSONStream.parse('data.items.');

// Для: глубоких структур или сложных условий

const parser = JSONStream.parse(['data', 'nested', { emitKey: true }]);

Сравнение производительности

| Метод | Размер файла | Использование памяти | Время |

|-------|-------------|---------------------|-------|

| JSON.parse() | 500MB | ~2.1GB 💥 | 8с |

| JSONStream | 500MB | ~45MB ✅ | 12с |

| stream-json | 500MB | ~38MB ✅ | 10с |

Бенчмарк: Node.js 20, Mac M1, 16GB RAM

Решение 2: stream-json (быстрее)

stream-json быстрее JSONStream и предоставляет больше контроля:
const { chain } = require('stream-chain');

const { parser } = require('stream-json');

const { streamArray } = require('stream-json/streamers/StreamArray');

const pipeline = chain([

fs.createReadStream('huge-array.json'),

parser(),

streamArray()

]);

pipeline.on('data', ({ key, value }) => {

console.log(Индекс ${key}:, value.name);

// Обработка здесь

processRecord(value);

});

pipeline.on('end', () => console.log('Готово!'));

Для JSON-объектов (не массивов)

const { streamObject } = require('stream-json/streamers/StreamObject');

const pipeline = chain([

fs.createReadStream('huge-object.json'),

parser(),

streamObject()

]);

pipeline.on('data', ({ key, value }) => {

console.log(Ключ: ${key}, value);

});

Решение 3: Чтение по частям (браузер)

В браузерах используйте FileReader для постепенной обработки файлов:

async function parseHugeJSON(file) {

const chunkSize = 1024 1024; // 1MB

let offset = 0;

let buffer = '';

let results = [];

while (offset < file.size) {

const chunk = file.slice(offset, offset + chunkSize);

const text = await chunk.text();

buffer += text;

// Попытка распарсить полные объекты

const lines = buffer.split('\n');

buffer = lines.pop(); // Сохранить неполную строку

for (const line of lines) {

if (line.trim()) {

try {

const obj = JSON.parse(line);

results.push(obj);

// Обработка пакетами для предотвращения накопления памяти

if (results.length >= 1000) {

await processBatch(results);

results = [];

}

} catch (e) {

console.warn('Пропуск недействительной строки:', line.substring(0, 50));

}

}

}

offset += chunkSize;

// Отчёт о прогрессе

const progress = (offset / file.size 100).toFixed(1);

console.log(Прогресс: ${progress}%);

}

// Обработка последнего пакета

if (results.length > 0) {

await processBatch(results);

}

}

// Использование

document.getElementById('fileInput').addEventListener('change', async (e) => {

const file = e.target.files[0];

await parseHugeJSON(file);

});

Решение 4: Web Workers (неблокирующая обработка)

Чтобы предотвратить зависание UI, переместите парсинг в Web Worker:

// worker.js

self.onmessage = async function(e) {

const { file } = e.data;

const chunkSize = 1024 1024;

let processed = 0;

for (let offset = 0; offset < file.size; offset += chunkSize) {

const chunk = file.slice(offset, offset + chunkSize);

const text = await chunk.text();

// Обработка части

const lines = text.split('\n').filter(l => l.trim());

const objects = lines.map(line => {

try {

return JSON.parse(line);

} catch {

return null;

}

}).filter(Boolean);

// Отправка результатов обратно

self.postMessage({

type: 'progress',

data: objects,

percent: (offset / file.size 100)

});

processed += objects.length;

}

self.postMessage({ type: 'complete', total: processed });

};

// main.js

const worker = new Worker('worker.js');

worker.onmessage = function(e) {

if (e.data.type === 'progress') {

console.log(Обработано: ${e.data.percent.toFixed(1)}%);

updateUI(e.data.data);

} else if (e.data.type === 'complete') {

console.log(Готово! Всего: ${e.data.total});

}

};

// Запуск обработки

const fileInput = document.getElementById('upload');

fileInput.addEventListener('change', (e) => {

worker.postMessage({ file: e.target.files[0] });

});

Решение 5: Пагинация API

Если вы получаете JSON из API, используйте пагинацию:

async function fetchAllData(apiUrl) {

let allData = [];

let page = 1;

let hasMore = true;

while (hasMore) {

const response = await fetch(${apiUrl}?page=${page}&limit=100);

const data = await response.json();

// Немедленная обработка страницы

await processPage(data.items);

allData.push(...data.items);

hasMore = data.hasNextPage;

page++;

// Добавление задержки для избежания ограничения скорости

await new Promise(resolve => setTimeout(resolve, 100));

}

return allData;

}

// Оптимизированная версия: не хранить всё

async function processAllData(apiUrl) {

let page = 1;

let hasMore = true;

let totalProcessed = 0;

while (hasMore) {

const response = await fetch(${apiUrl}?page=${page}&limit=100);

const data = await response.json();

// Обработка без хранения

for (const item of data.items) {

await processItem(item); // Сохранение в БД, преобразование и т.д.

totalProcessed++;

}

hasMore = data.hasNextPage;

page++;

console.log(Обработано ${totalProcessed} элементов...);

}

return totalProcessed;

}

Решение 6: Python с ijson

Для серверной обработки отлично подходит Python's ijson:

import ijson

def process_large_json(filename):

with open(filename, 'rb') as file:

# Парсинг массива объектов

objects = ijson.items(file, 'item')

count = 0

for obj in objects:

# Обработка каждого элемента

if obj.get('price', 0) > 1000:

save_to_database(obj)

count += 1

if count % 1000 == 0:

print(f"Обработано {count} записей...")

print(f"Всего: {count} записей")

# Для вложенных структур

def process_nested(filename):

with open(filename, 'rb') as file:

# Извлечение только определённых ключей

for user_id in ijson.items(file, 'users.item.id'):

print(f"ID пользователя: {user_id}")

Стратегии оптимизации

1. Пакетная обработка вместо индивидуальной

let batch = [];

const BATCH_SIZE = 1000;

parser.on('data', async (item) => {

batch.push(item);

if (batch.length >= BATCH_SIZE) {

await insertBatch(batch); // Одна пакетная вставка

batch = [];

}

});

parser.on('end', async () => {

if (batch.length > 0) {

await insertBatch(batch); // Обработка последнего пакета

}

});

2. Ранняя фильтрация данных

const { pick } = require('stream-json/filters/Pick');

const { streamArray } = require('stream-json/streamers/StreamArray');

const pipeline = chain([

fs.createReadStream('data.json'),

parser(),

pick({ filter: 'users' }), // Только свойство "users"

streamArray()

]);

3. Сжатие файлов

const zlib = require('zlib');

const pipeline = chain([

fs.createReadStream('data.json.gz'),

zlib.createGunzip(), // Распаковка на лету

parser(),

streamArray()

]);

Типичные сценарии и решения

Сценарий 1: Импорт базы данных из JSON

const { chain } = require('stream-chain');

const { parser } = require('stream-json');

const { streamArray } = require('stream-json/streamers/StreamArray');

const { MongoClient } = require('mongodb');

async function importToMongo(filename, collectionName) {

const client = await MongoClient.connect('mongodb://localhost:27017');

const collection = client.db('mydb').collection(collectionName);

let batch = [];

const BATCH_SIZE = 1000;

const pipeline = chain([

fs.createReadStream(filename),

parser(),

streamArray()

]);

for await (const { value } of pipeline) {

batch.push(value);

if (batch.length >= BATCH_SIZE) {

await collection.insertMany(batch);

console.log(Вставлено ${batch.length} документов);

batch = [];

}

}

if (batch.length > 0) {

await collection.insertMany(batch);

}

await client.close();

}

Сценарий 2: Преобразование JSON в CSV

const { Transform } = require('stream');

const csvWriter = require('csv-write-stream');

const jsonToCSV = new Transform({

objectMode: true,

transform(chunk, encoding, callback) {

// Преобразование JSON-объекта в строку CSV

callback(null, {

id: chunk.id,

name: chunk.name,

email: chunk.email

});

}

});

const writer = csvWriter();

writer.pipe(fs.createWriteStream('output.csv'));

pipeline

.pipe(jsonToCSV)

.pipe(writer);

Сценарий 3: Обработка JSON с разделителями строк (NDJSON)

const readline = require('readline');

async function processNDJSON(filename) {

const fileStream = fs.createReadStream(filename);

const rl = readline.createInterface({

input: fileStream,

crlfDelay: Infinity

});

let count = 0;

for await (const line of rl) {

if (line.trim()) {

const obj = JSON.parse(line);

await processObject(obj);

count++;

}

}

console.log(Обработано ${count} строк);

}

Устранение неполадок

Ошибка: JavaScript heap out of memory

Решение:
# Увеличение лимита памяти в Node.js

node --max-old-space-size=4096 script.js

Но лучше: Используйте потоковую передачу!

Ошибка: Недействительный JSON при чтении по частям

Проблема: Части могут разделить JSON-объекты. Решение: Используйте JSON с разделителями строк (NDJSON):
// Сначала конвертировать JSON в NDJSON

const items = require('./data.json'); // Загрузить небольшой файл один раз

const stream = fs.createWriteStream('data.ndjson');

items.forEach(item => {

stream.write(JSON.stringify(item) + '\n');

});

stream.end();

Ошибка: Процесс слишком медленный

Оптимизации:
  • Используйте stream-json вместо JSONStream (в 2 раза быстрее)
  • Увеличьте размер пакета (1000 → 5000)
  • Распараллельте вставки в БД
  • Используйте пулы соединений БД
  • Добавьте индексы перед массовой вставкой
  • Рекомендуемые инструменты

    Для валидации JSON

    Используйте BigJSON.online для быстрой валидации и проверки больших JSON-файлов без их полной загрузки. Поддерживает потоковую передачу и валидацию в реальном времени.

    Библиотеки

    • Node.js: stream-json (самый быстрый), JSONStream (самый простой)
    • Python: ijson (лучший)
    • Браузер: FileReader API + Web Workers

    Заключение

    Быстрые рекомендации:

    | Сценарий использования | Лучшее решение | Использование памяти |

    |----------------------|---------------|---------------------|

    | Node.js (большие массивы) | stream-json | Очень низкое |

    | Node.js (простые структуры) | JSONStream | Низкое |

    | Браузер (< 50MB) | Чтение по частям | Среднее |

    | Браузер (> 50MB) | Web Workers | Низкое |

    | Загрузка API | Пагинация + потоковая обработка | Очень низкое |

    | Python | ijson | Очень низкое |

    Ключевые моменты:
    • Всегда используйте потоковую передачу для файлов > 50MB
    • Пакетная обработка для вставок в БД
    • Ранняя фильтрация для снижения использования памяти
    • Не используйте JSON.parse() для больших файлов
    • Не загружайте всё сразу в память

    Используйте современные пакеты для потоковой передачи, и вы сможете легко обрабатывать JSON-файлы размером в гигабайты без исчерпания памяти или сбоев.

    Дополнительные ресурсы

    Нашли это руководство полезным? Поделитесь им с коллегами-разработчиками, которые борются с большими JSON-файлами!

    Share:

    Похожие статьи

    Read in English