Ускорение парсинга большого JSON
Главное: Оптимизация в неправильном порядке делает бесполезной любую замену библиотеки. Измеряйте сеть, парсинг и отрисовку отдельно, затем сократите число парсингов, уменьшите полезную нагрузку и используйте потоковую обработку.

Главное: сначала измеряйте сеть, парсинг и отрисовку отдельно — большинство «медленного парсинга» оказывается медленной отрисовкой — затем поднимайтесь по лестнице по порядку: меньше парсингов, меньше полезная нагрузка, потоковая обработка, worker, и двоичный формат в последнюю очередь.
Не начинайте с замены библиотек. Добавьте один таймер, и вы обычно обнаружите, что проблема в отрисовке.
Шаг первый: измерьте этапы
const t0 = performance.now();
const response = await fetch(url);
const text = await response.text();
const t1 = performance.now();
const data = JSON.parse(text);
const t2 = performance.now();
render(data);
const t3 = performance.now();
| Доминирующий этап | Настоящая проблема | Исправление |
|---|---|---|
| Сеть | Полезная нагрузка слишком велика | Выбор полей, пагинация, сжатие |
JSON.parse |
Действительно большой ввод | Меньше парсингов, потоковая обработка, worker |
| Отрисовка | Десятки тысяч узлов сразу | Виртуальный список, постраничный просмотр, свёртывание |
Современные браузеры разбирают 1 МБ примерно за 5–15 мс, а 10 МБ — за 80–150 мс. Если 10 МБ занимают две секунды, JSON.parse — не ваше узкое место.
Шаг второй: парсите меньше
Самая дешёвая победа и самая упускаемая.
- Прекратите разбирать одну и ту же полезную нагрузку повторно. Десять компонентов, каждый вызывающий
JSON.parse(localStorage.getItem(...)), — это десять парсингов. Кешируйте один раз на уровне модуля. - Прекратите использовать
JSON.parse(JSON.stringify(x))для глубоких копий. Это стоит больше чем вдвое объёма данных;structuredClone— нет. - Не парсите в главном потоке и затем не отправляйте объект в worker.
postMessageклонирует его снова. Отправляйте сырой текст и позволяйте worker его разбирать. - Просите меньше полей. Если интерфейсу нужны три свойства, API не должен отправлять триста.
Шаг третий: уменьшите полезную нагрузку
Пагинация по page/size, курсоры вместо глубокого offset, сводные поля в конечных точках списков. Это обычно превосходит все фронтенд-оптимизации вместе, потому что снижает сеть, парсинг и память одновременно.
Шаг четвёртый: потоковая обработка
Когда данные нужно обработать целиком и они действительно велики:
import { parser } from 'stream-json';
import { streamArray } from 'stream-json/streamers/StreamArray';
const pipeline = fs.createReadStream('big.json').pipe(parser()).pipe(streamArray());
for await (const { value } of pipeline) {
await handleOne(value);
}
Больше кода и нет произвольного доступа — подходит только для обработки запись за записью.
Шаг пятый: уберите из главного потока
Парсинг быстрый, но блокирующий. Поместите его в worker и передавайте текст, а не разобранный объект, иначе структурированное клонирование удвоит цену.
Шаг шестой: смените формат, в последнюю очередь
Для внутреннего трафика, стабильно измеряемого десятками мегабайт, NDJSON естественно потоковый, а Protobuf или MessagePack меньше и быстрее. Оба — изменения протокола, требующие координации.
Не делайте
evalJSON: небезопасно и не быстрее- Стрингифицировать JSON, чтобы вложить его в JSON: удваивает размер
- Разбирать сотни тысяч крошечных строк в цикле: пакетируйте их
Вопросы и ответы
Как понять, парсинг или отрисовка — узкое место?
Замеряйте отдельно запрос, чтение текста, JSON.parse и отрисовку через performance.now() и считайте самый длительный этап настоящим узким местом. Современные браузеры разбирают 1 МБ примерно за 5–15 мс, а 10 МБ — за 80–150 мс, поэтому полезная нагрузка в 10 МБ, занимающая две секунды, редко является проблемой JSON.parse.
Почему замена библиотек обычно не помогает?
JSON.parse уже нативный и быстрый; большинство «медленного парсинга» происходит от повторного парсинга или отрисовки десятков тысяч узлов сразу. Замена библиотеки даёт 10–30%, тогда как удаление одного полного парсинга часто экономит половину.
Стоит ли переносить парсинг в Web Worker?
Переносите в worker, когда парсинг блокирует главный поток, а страница зависает или анимации теряют кадры. Передавайте сырой текст, а не разобранный объект, иначе структурированное клонирование через postMessage удвоит цену.