JSON: обмен данными

коллекции≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Объект живёт в памяти и сам никуда не уедет: чтобы отправить данные по сети или положить в файл, нужен текст. JSON — общий для всех языков способ записать структуру строкой и собрать её обратно.

Разделы темы:·

Внутри программы данные живут объектами и массивами. Но стоит отправить их по сети или сохранить в файл — и структура исчезает: наружу уходят только байты, то есть текст. JSON — договор о том, как записать структуру текстом, чтобы на другом конце её собрали обратно точно такой же.

Синтаксис JSON вырос из объектов JavaScript, но сам формат давно ничей: его читают Python, Go, PHP, PostgreSQL и любой HTTP-клиент. Победил он скукой — шесть типов значений (объект, массив, строка, число, true/false, null) и никаких схем, тегов и атрибутов, как в XML. Правила при этом жёсткие: ключи и строки только в двойных кавычках, комментариев нет, висячая запятая запрещена.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

JSON.stringify берёт значение и возвращает строку. Вглубь он спускается сам: вложенные объекты и массивы обрабатываются рекурсивно, склеивать текст вручную не нужно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Заметьте: в результате нет ни одного лишнего пробела, а ключи стоят в кавычках, хотя в исходном объекте кавычек не было. Это не вопрос стиля — так требует формат.

Если в структуре есть цикл — объект прямо или через цепочку ссылается на самого себя, — рекурсия ушла бы в бесконечность. Поэтому JSON.stringify сдаётся и бросает ошибку.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

JSON.parse делает обратное: читает строку и строит из неё значение. Обратные кавычки в примере — просто способ написать строку с двойными кавычками внутри, к JSON они отношения не имеют.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Слово «строит» здесь важное: получается совершенно новое значение, никак не связанное с исходным объектом. На этом держится старый трюк глубокого копирования — объект прогоняют в текст и обратно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Копия через JSON — трюк на скорую руку. Для честной копии сегодня есть structuredClone: он умеет Date, Map и Set.

JSON знает только свои шесть типов, поэтому всё остальное он либо выбрасывает, либо превращает во что-то похожее — молча, без предупреждений.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
Что в JavaScriptЧто попадёт в JSON
функциясвойство исчезает
undefinedсвойство исчезает, а в массиве становится null
Dateстрока в формате ISO
NaN, Infinitynull
Map, Setпустой объект {}
BigIntошибка TypeError

Отдельная ловушка — даты. Date уезжает читаемой ISO-строкой, но обратно строка датой не становится: разбор вернёт именно строку, и getFullYear у неё уже не спросишь.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Третий аргумент JSON.stringify — отступ. Число означает столько пробелов на уровень, строка (например "\t") — свой символ отступа. Второй аргумент при этом обычно не нужен, поэтому на его месте ставят null.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Второй аргумент — replacer, фильтр. Массив строк оставит только перечисленные ключи; функция вызывается для каждой пары и решает, что записать. Удобно, когда в объекте есть то, чему не место в логах.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

У JSON.parse тоже есть второй аргумент — reviver. Он вызывается для каждой пары «ключ — значение», и то, что вернули, попадает в результат. Это штатный способ вернуть строкам их настоящий тип.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Строка, пришедшая извне, — это не обещание корректного JSON. Пустой ответ, HTML страницы ошибки вместо данных, лишняя запятая — и JSON.parse бросает SyntaxError, а без обработки падает весь код вокруг. Перехватить такую ошибку помогает конструкция try...catch: код, который может сломаться, кладут в блок try, и если там брошена ошибка, выполнение не обрывается, а переходит в блок catch. Туда же приходит объект ошибки, и по его полю name видно, что случилось. Подробно конструкцию разбирает статья «try..catch: перехват ошибок».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Поэтому чужой текст разбирают в try...catch. Чтобы не повторять одно и то же в каждом месте, заводят маленькую обёртку с запасным значением. Объект ошибки ей не нужен, поэтому скобки после catch опущены — так тоже можно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

  • JSON — текстовый формат обмена данными: структура уходит строкой и собирается обратно, причём в любом языке.
  • JSON.stringify превращает значение в строку, JSON.parse строит из строки новое значение — не связанное с исходным.
  • Функции, undefined, Map и Set сериализацию не переживают, NaN становится null, а Date уезжает строкой и сама датой не возвращается.
  • Третий аргумент stringify — отступ для читаемого вывода, второй — replacer-фильтр; у parse второй аргумент — reviver, который умеет чинить типы.
  • На некорректном тексте JSON.parse бросает SyntaxError, поэтому данные из сети разбирают в try...catch.

1/3Что выведет console.log(JSON.stringify({ a: undefined, b: 1 }))?

2/3Объект с полем Date прогнали через JSON.stringify, а потом через JSON.parse. Чем окажется это поле?

3/3За что отвечает третий аргумент JSON.stringify?

Довезти данные целыми

Объект состояния сериализуется с потерями: посмотрите, что стало с полем visited. Приведите данные к тому, что JSON умеет, и получите строку, в которой уцелели все три поля.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Set для JSON — обычный объект без собственных свойств, отсюда пустые фигурные скобки. Дата выживает только потому, что у Date есть метод toJSON. Значит, перед сериализацией собираем «плоскую» копию из того, что формат понимает.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Тот же результат даёт метод toJSON у самого объекта: если он есть, JSON.stringify вызовет его и запишет возвращённое значение. И помните про обратный путь — из массива Set придётся собирать самим.

Спрятать пароли в логе

Этот объект уходит в лог целиком, вместе с паролями — в том числе вложенными. Сделайте так, чтобы вместо любого значения ключа password на любой глубине попадало "***". Остальные поля должны остаться нетронутыми.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Пишем replacer-функцию. Её вызывают для каждой пары на каждом уровне вложенности, поэтому рекурсию писать не нужно — достаточно проверить ключ и вернуть либо заглушку, либо исходное значение.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Важно вернуть value в ветке «иначе»: если функция вернёт undefined, свойство просто исчезнет из результата. Кстати, самый первый вызов приходит с пустым ключом и всем объектом целиком — это нормально.