JSON Toolkit

Ускорение парсинга большого JSON

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

Средний~5 мин чтенияОпубликовано 2026-10-30Обновлено 2026-10-30
Порядок оптимизации парсинга большого 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 меньше и быстрее. Оба — изменения протокола, требующие координации.

Не делайте

  • eval JSON: небезопасно и не быстрее
  • Стрингифицировать JSON, чтобы вложить его в JSON: удваивает размер
  • Разбирать сотни тысяч крошечных строк в цикле: пакетируйте их

Вопросы и ответы

Как понять, парсинг или отрисовка — узкое место?

Замеряйте отдельно запрос, чтение текста, JSON.parse и отрисовку через performance.now() и считайте самый длительный этап настоящим узким местом. Современные браузеры разбирают 1 МБ примерно за 5–15 мс, а 10 МБ — за 80–150 мс, поэтому полезная нагрузка в 10 МБ, занимающая две секунды, редко является проблемой JSON.parse.

Почему замена библиотек обычно не помогает?

JSON.parse уже нативный и быстрый; большинство «медленного парсинга» происходит от повторного парсинга или отрисовки десятков тысяч узлов сразу. Замена библиотеки даёт 10–30%, тогда как удаление одного полного парсинга часто экономит половину.

Стоит ли переносить парсинг в Web Worker?

Переносите в worker, когда парсинг блокирует главный поток, а страница зависает или анимации теряют кадры. Передавайте сырой текст, а не разобранный объект, иначе структурированное клонирование через postMessage удвоит цену.

Дополнительное чтение