Ни для кого не секрет, что JSON широко используется в веб-разработке: обмен данными между клиентом (браузером) и сервером, хранение в NoSQL-базах, конфигурационные файлы, API-ответы и многое другое. Он стал практически родным форматом данных для JavaScript и Node.js. Однако при работе с JSON стоит учитывать ряд ограничений и подводных камней, которые в больших проектах могут вылиться в серьёзные проблемы с производительностью, точностью и безопасностью.
В этой статье мы разберём:
Неочевидные проблемы при сериализации/десериализации JSON - с фокусом на веб-разработку.
Обработку больших JSON-файлов - нюансы и инструменты в Node.js (и не только).
Популярные альтернативы JSON: MessagePack и Protocol Buffers - когда и как их стоит применять в веб-приложениях.
Статья рассчитана на веб-разработчиков, которые работают с JSON каждый день и хотят глубже разобраться в его особенностях, а также расширить свой стек инструментов.
Неочевидные проблемы при сериализации и десериализации JSON (веб-контекст)
Большие числа и потеря точности
В JavaScript (и, соответственно, в браузере и Node.js) максимальное безопасное целое число равно 2^53 - 1. Если вы храните ID или денежные суммы, которые превышают этот порог, то при парсинге JSON может произойти потеря точности.
const jsonString = '{"order_id": 1234567890123456789, "price": 1499.95}';
const data = JSON.parse(jsonString);
console.log(data.order_id);
// 1234567890123456800 — ошибка, хвост числа "округлился"
Рекомендации:
Храните слишком большие целые числа в JSON как строки:
{"order_id": "1234567890123456789"}
.В Node.js (начиная с версии 10.4, а также в современных браузерах) можно использовать BigInt для точных вычислений:
BigInt("1234567890123456789")
. Но в JSON по-прежнему придётся обрабатывать как строку.Если нужно передавать огромные суммы/балансы в финансовом контексте, рассмотрите передачу в строчном формате либо используйте специализированные решения (двоичные протоколы, см. раздел про ProtoBuf).
Даты и время
Стандарт JSON не содержит встроенного типа даты/времени. В веб-среде чаще всего встречаются три подхода:
ISO 8601:
2025-01-04T12:34:56Z
Unix Timestamp (в секундах или миллисекундах):
1672822561000
Пользовательские форматы:
04/01/2025 12:34:56
,2025.01.04 12:34:56
и т.д.
Проблемы возникают, когда мы забываем учитывать:
Браузер хранит дату внутри объекта
Date
в формате UTC, но при выводе преобразует её в локальный часовой пояс, что может вызывать путаницу при обработке времени.Несогласованность форматов. Например, сервер выдаёт
"2025-01-04T12:34:56Z"
, а кто-то пытается парсить его какMM/DD/YYYY
.Некоторые библиотеки или операции могут обрезать миллисекунды из даты, что приводит к расхождению данных между различными системами.
Как решать:
Соблюдайте единый формат для дат, чаще всего используют ISO 8601 (UTC).
-
На клиенте используйте проверенные библиотеки: date-fns, Moment.js (находится в режиме поддержания и не развивается ), Day.js, Luxon.
Также обратите внимание на новый Temporal API (пока в стадии черновика), который потенциально может заменить Date в JavaScript.
На сервере (Node.js) для хранения и обработки дат в базе данных (например, PostgreSQL, MongoDB) старайтесь приводить всё к UTC.
Экранирование спецсимволов, Unicode и эмодзи
JSON-строки должны экранировать спецсимволы (\n
, \t
, \"
, \\
). Если нужно передавать эмодзи или символы за пределами U+FFFF
, то фактически они превращаются в суррогатные пары (например, \ud83d\ude00
для ?).
В большинстве случаев это прозрачно для веб-разработчика, но могут возникнуть проблемы:
Если JSON-строка несколько раз проходит через процесс сериализации, каждый этап добавляет обратные слеши, что может привести к накоплению экранированных символов и, в конечном итоге, к сложночитаемому "лесу" слешей.
Неверная работа с Unicode на этапах парсинга сторонними библиотеками или плагинами.
const data = {
text: "Hello\nNewLine",
emoji: "?"
};
const str = JSON.stringify(data);
console.log(str);
// {"text":"Hello\nNewLine","emoji":"\ud83d\ude00"}
const parsed = JSON.parse(str);
console.log(parsed);
// { text: 'Hello\nNewLine', emoji: '?' }
Обычно всё хорошо, если использовать стандартный JSON.parse
/JSON.stringify
, но если у вас сложный пайплайн (например, преобразование на стороне клиентских библиотек, несколько слоёв API), стоит убедиться, что все компоненты корректно обрабатывают экранирование.
Производительность
Веб-разработчики, создавая REST API, часто генерируют JSON-ответы на десятки мегабайт, а потом удивляются долгому парсингу и высокому расходу памяти.
На стороне клиента:
JSON.parse
крупного ответа (особенно в мобильном браузере) может подвесить UI на несколько секунд.На стороне сервера (Node.js):
JSON.stringify
очень большого объекта тоже затратен.
Если ваше веб-приложение возвращает огромные JSONы:
Подумайте, действительно ли нужно отдавать всё сразу. Возможно, лучше сделать пагинацию, lazy-load или частичные данные.
Используйте стриминг там, где это уместно (Node.js
stream
+ JSON chunking).Сжимайте ответ (например,
gzip
илиbrotli
в Express через compression).
Обработка больших JSON-файлов на Node.js
Стриминг и потоки (Streams API)
В Node.js крайне желательно обрабатывать большие JSON-файлы (или потоки данных) поэтапно, а не загружать их полностью в память.
SAX-подобные парсеры для JSON: stream-json, JSONStream.
Чтение файл → парсер → обработка: вы считываете файл через
fs.createReadStream
, передаёте в стриминговый парсер, который эмитит объекты по мере чтения.
Пример (используя stream-json
):
const fs = require('fs');
const { parser } = require('stream-json');
const { streamValues } = require('stream-json/streamers/StreamValues');
const readStream = fs.createReadStream('big.json');
readStream
.pipe(parser())
.pipe(streamValues())
.on('data', (data) => {
// data.value содержит очередной кусок JSON
console.log(data.value);
})
.on('end', () => {
console.log('Done processing large JSON!');
});
Таким образом, Node.js не хранит весь JSON в памяти, а обрабатывает по частям.
NDJSON / JSON Lines
Если у вас много однотипных объектов, рассмотрите формат NDJSON (Newline-Delimited JSON). Каждая строка - отдельный JSON-объект:
{"id":1,"name":"Item1"}
{"id":2,"name":"Item2"}
{"id":3,"name":"Item3"}
Читать такой файл через стримы в Node.js очень удобно. Можно построчно обрабатывать:
const fs = require('fs');
const readline = require('readline');
async function processNDJSON(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({ input: fileStream });
for await (const line of rl) {
const obj = JSON.parse(line);
// Обработка obj
console.log(obj.name);
}
}
processNDJSON('items.ndjson');
Преимущества:
Не надо парсить огромный массив, можно сразу по строкам.
Если данные идут в реальном времени (например, лог-сервис), то NDJSON позволяет обрабатывать их на лету.
Компрессия
Для экономии места и ускорения передачи больших JSON-ответов по HTTP в продакшене практически всегда включают gzip или brotli-сжатие:
const express = require('express');
const compression = require('compression');
const app = express();
app.use(compression()); // Включаем сжатие
app.get('/api/data', (req, res) => {
const bigObject = generateHugeObject();
res.json(bigObject);
});
app.listen(3000, () => {
console.log('Server running...');
});
На клиенте браузер автоматически декомпрессирует ответ, остаётся лишь распарсить JSON. Если же ваш сервис передаёт большие JSON-файлы другой системе, убедитесь, что другая сторона тоже умеет декомпрессировать (обычно это стандарт).
Альтернативы JSON: MessagePack и Protocol Buffers для веб
Это двоичный формат, который сохраняет структуру данных, похожую на JSON (объекты, массивы, строки, числа), но в более компактном виде.
Плюсы:
Меньший размер данных (на 20-50% меньше по сравнению с JSON).
Высокая скорость парсинга (не нужно разбирать текст).
Поддерживается во многих языках, включая JavaScript/Node.js (msgpack5).
Минусы:
Меньшая человеко-читаемость (в браузере не так удобно дебажить).
Нужно сторонними средствами смотреть, что внутри (нужен декодер).
Пример (Node.js с msgpack5):
const msgpack = require('msgpack5')();
const data = {
user: 'Alice',
age: 30,
scores: [10, 20, 30]
};
const packed = msgpack.encode(data);
console.log('Packed buffer:', packed);
const unpacked = msgpack.decode(packed);
console.log('Unpacked:', unpacked);
В реальном проекте MessagePack может дать выигрыш в скорости и объёме передаваемых данных, особенно когда речь идёт о высоконагруженных сервисах.
Protocol Buffers (Protobuf) - двоичный формат от Google, в котором обязательно описывать структуру данных в .proto
-файле (схема). На базе этой схемы генерируется код (классы) для различных языков.
Плюсы:
Высокая производительность (парсинг, размер данных).
Строгая типизация и версионирование.
Идеально подходит для микросервисов на gRPC.
Минусы:
Нужно поддерживать
.proto
-схему и генерировать код.Меньшая гибкость в сравнении с JSON (сложно передавать «произвольные» структуры).
Упрощенный пример. Схема (user.proto
):
syntax = "proto3";
message User {
string name = 1;
int32 age = 2;
repeated int32 scores = 3;
}
Устанавливаем protoc и плагин для Node.js, генерируем JS-код. В итоге получаем файлы вида user_pb.js
.
const messages = require('./user_pb'); // сгенерированный код
const user = new messages.User();
user.setName('Alice');
user.setAge(30);
user.setScoresList([10, 20, 30]);
const bytes = user.serializeBinary();
console.log('Binary length:', bytes.length);
const user2 = messages.User.deserializeBinary(bytes);
console.log(user2.getName(), user2.getAge(), user2.getScoresList());
Применять Protobuf в веб-разработке имеет смысл, если вы строите масштабируемую систему микросервисов на gRPC или действительно заботитесь о каждом килобайте и миллисекунде. Однако для большинства веб-API, где удобнее быстро смотреть структуру в сыром виде, JSON остаётся основным форматом.
Заключение и рекомендации для веб-разработчиков
Проверяйте точность чисел - не полагайтесь на то, что большие
order_id
илиbalance
всегда поместятся в Number.Храните и передавайте даты в едином формате (ISO 8601 с UTC) - чтобы избежать путаницы с часовыми поясами.
Стримьте большие JSON - Node.js позволяет легко обрабатывать файлы и потоки, не загружая всё в память.
Используйте компрессию (
gzip
,brotli
) - это ускорит передачу JSON через HTTP.Рассмотрите альтернативы (MessagePack, Protobuf), если у вас высокие требования к производительности и объёму трафика и вы готовы поддерживать двоичный формат (особенно когда надо экономить ресурсы).
Вместо послесловия
В большинстве веб-проектов JSON остаётся удобным и достаточным решением. Однако стоит помнить о его подводных камнях: потеря точности чисел, отсутствие встроенной даты, большие объёмы данных, которые могут убить производительность и съесть всю память. Если вы развиваете сложный сервис с высоконагруженными компонентами, возможно, стоит присмотреться к более эффективным форматам, вроде MessagePack или ProtoBuf. Но для подавляющего большинства случаев JSON в связке с Node.js (и правильным стримингом/компрессией) будет надёжным выбором.
Удачной веб-разработки!
Комментарии (6)
andry36 Автор
12.01.2025 07:06спасибо за фидбек!
соглашусь, что подобные сложности (большие числа, даты, объёмные данные) могут возникать в любом текстовом формате, не только JSON. Однако JSON сегодня является стандартом для большинства веб-приложений на JavaScript/Node.js, поэтому именно в нём эти грабли встречаются чаще всего. Статья призвана показать, как обходить их в контексте JSON и когда стоит рассмотреть альтернативные форматы.
Правильно ли я понимаю, что вас смущает именно то, что в статье рассмотрены проблемы в контексте JSON (хотя они встречаются и в других форматах), и сам заголовок подаёт их как уникальные для JSON?
dyadyaSerezha
12.01.2025 07:06Комментатор прав в том, что, исходя из названия статьи, ожидаешь увидеть нечто другое. В статье же обсуждаются не скрытые ошибки/проблемы JSON (от слова "подвести"), а совершенно публичные и понятные свойства или ограничения формата. Поэтому название и содержание статьи несколько не коррелируют.
Это как написать в статье "jpeg. может вас подвести", что при стриминге видео не самая удачная идея пересылать отдельные кадры как jpeg-картинки, а лучше использовать специальный видео-формат с гораздо большим сжатием и другими фишками.
mds-oof
12.01.2025 07:06Я, конечно, понимаю, что хочется вставить красивое изображение в пост. Но изображения от ИИ все же надо обрабатывать. Иначе создается ощущение, что писали наспех и лишь бы опубликовать.
Давайте вместе посмотрим на изображение. В центре "файл" JSON. А что внутри него? Скобочки, музыкальные ноты, дефисы (не говоря уже про надписи "JSON Pastlets" вокруг, чтобы это не значило). Чтобы исправить это потребуется примерно минут 5-10. Затереть эту глупость и вставить похожим шрифтом хотя бы что-то отдаленно напоминающее JSON, или вообще оставив пустой файлик. И это смотрелось бы гармоничнее. И статья выглядела бы более зрело.lightman
12.01.2025 07:06+1
Неужели авторы постов не видят, как отвратительно выглядят эти изображения, что изображения с искажениями вызывают какое-то глубокое отторжение из разряда эффекта зловещей долины?
Хотел тут послушать подборку рождественских песен на нельзятубе - так весь ролик был сгенерирован ИИ. Какое-то время боролся с собой, старался не смотреть, только слушать (песни то классические, прекрасные), но не смог, переключил.
Решил отдохнуть от компьютера, вышел на улицу прогуляться - так даже на уличном баннере вставили на фон. Просто какая-то эпидемия. Или т.н. "контент мейкеры" считают что "да пофиг, они не заметят разницы". Ещё как замечаем! Омерзительная искажённая бездушность ИИ изображений и видео сразу бросается глаза - неужели у вас атрофировано чувство эстетики и вы сами этого не видите?
Администрация хабра, пожалуйста, добавьте в список причин при минусе поста пункт "использование ИИ изображений". Сейчас приходится выбирать "пост небрежно оформлен", но это не совсем то и не даёт автору полезной обратной связи.
monochromer
12.01.2025 07:06Для экономии места и ускорения передачи больших JSON-ответов по HTTP в продакшене практически всегда включают gzip или brotli-сжатие
app.use(compression()); // Включаем сжатие
Только сжатие лучше перенести на уровень proxy-сервера, например, Nginx или Caddy.
somech
При чем тут вообще JSON во всех приведенных проблемах? Давайте разберем заключение:
1-2 -- JSON вообще не при чем. В XML будут те-же проблемы. Да даже со стрингой.
3 -- проблемы большого объема данных, актуально для любого формата.
4 -- максимально капитанское объяснение, которое, опять таки, не проблема конкретно JSONа как формата данных. Не сжимает данные нынче только ленивый
5 -- альтернативы есть. Ну хорошо.
Подведя собственное "заключение" :а
был липри чем тут JSON?