JSON в JavaScript
Работа с JSON в JavaScript: разбор и сериализация данных, ревиверы и заменители, форматирование вывода, глубокое копирование объектов, даты и типичные ошибки.
JavaScript Object Notation (JSON) — это основа обмена данными в вебе. Почти каждый вызываемый вами API возвращает JSON, большинство конфигурационных файлов написаны в JSON, и когда браузер общается с сервером, именно JSON чаще всего передаётся по сети. Это руководство охватывает всё необходимое для уверенной работы с JSON в JavaScript: синтаксис, два основных метода (JSON.parse и JSON.stringify), форматирование и фильтрацию вывода, работу с датами и глубокое копирование, а также типичные ошибки, которые возникают в рабочем коде.
Что такое JSON и зачем он нужен
JSON — это лёгкий текстовый формат данных, удобный для чтения и записи людьми, а также для разбора и генерации машинами. Он не зависит от языка программирования — несмотря на то что его синтаксис заимствован из JavaScript (Standard ECMA-262), практически каждый язык (Python, Java, C#, Go, Rust, PHP) умеет читать и писать JSON. Именно эта универсальность сделала его стандартным форматом для REST API, конфигурационных файлов (package.json, tsconfig.json) и localStorage.
Главное, что нужно понять: JSON — это string, а не object. JSON-строка становится пригодным для использования объектом JavaScript только после разбора, а объект JavaScript становится JSON только после сериализации. Большая часть работы с JSON — это переход между этими двумя состояниями.
Базовый синтаксис JSON
Синтаксис JSON является строгим подмножеством синтаксиса литералов объектов JavaScript:
- Данные записываются как пары
"ключ": значение - Пары разделяются запятыми
- Фигурные скобки
{}содержат объекты, квадратные скобки[]— массивы - Ключи обязательно должны быть строками в двойных кавычках (одинарные кавычки и ключи без кавычек недопустимы)
- Значениями могут быть string, число, boolean,
null, object или array — но неundefined, функция или объектDate
{
"name": "John",
"age": 30,
"isDeveloper": true,
"languages": ["JavaScript", "Python", "Rust"]
}Разбор JSON с помощью JSON.parse()
JSON.parse() превращает JSON-строку в объект JavaScript (или массив, число и т.д.), с которым можно читать и работать. В реальном коде — особенно когда JSON приходит из сетевого запроса или пользовательского ввода — всегда оборачивайте его в try...catch, потому что некорректный JSON выбрасывает SyntaxError.
Доступ к вложенным данным
Разобранный JSON ведёт себя как любой другой object, поэтому к вложенным значениям можно обращаться с помощью точечной или скобочной нотации. Это наиболее распространённая операция при работе с ответом API.
Преобразование значений с помощью ревивера
JSON.parse() принимает необязательную функцию reviver в качестве второго аргумента. Она вызывается для каждой пары ключ/значение, и то, что она возвращает, становится итоговым значением — удобно для преобразования типов при разборе. Классический пример использования — восстановление объектов Date (в JSON нет типа даты, поэтому даты приходят как строки).
Сериализация объектов с помощью JSON.stringify()
JSON.stringify() выполняет обратное действие: преобразует значение JavaScript в JSON-строку, готовую к отправке по сети или сохранению в хранилище.
Форматирование с отступами
Третий аргумент управляет отступами. Передайте число (количество пробелов) или строку для получения читаемого, форматированного JSON — удобно для логов, конфигурационных файлов и отладки.
Фильтрация свойств с помощью заменителя
Второй аргумент — replacer. В виде массива ключей он определяет белый список сохраняемых свойств — быстрый способ убрать конфиденциальные поля, например пароли, перед отправкой данных.
В виде функции заменитель вызывается для каждого ключа и позволяет преобразовывать или исключать значения. Возврат undefined из него приводит к пропуску соответствующего свойства.
Пользовательская сериализация с помощью toJSON
Вы можете контролировать сериализацию конкретного object, добавив ему метод toJSON(). Когда JSON.stringify() встречает object с таким методом, он вызывает toJSON() и сериализует то, что тот возвращает. (Именно так объекты Date производят ISO-строки — у них есть встроенный toJSON.)
Что stringify молча пропускает
JSON.stringify() намеренно является неполным преобразованием. Зная, что именно оно пропускает, можно избежать неожиданных ошибок:
undefined, функции и значенияSymbolисключаются из объектов (а в массивах становятсяnull).- Объекты
Dateпревращаются в ISO-строки (через ихtoJSON). NaNиInfinityстановятсяnull.BigIntвыбрасываетTypeError.- Циклические ссылки выбрасывают
TypeError.
Продвинутые техники и распространённые паттерны
Помимо двух основных методов, повседневная работа с JSON включает глубокое копирование, работу с датами и обработку массивов записей.
Глубокое копирование объектов
Приём JSON.parse(JSON.stringify(obj)) создаёт глубокую копию — клон, вложенные объекты которого полностью независимы от оригинала, поэтому изменение одного никогда не влияет на другой.
Это быстрый способ без зависимостей, но он наследует все ограничения stringify: теряет функции, undefined и Symbol, превращает Date в строки и выбрасывает исключение при циклических ссылках. Для полноценного клонирования используйте встроенный structuredClone(), который корректно обрабатывает даты, Map, Set и циклы.
Работа с датами
В JSON нет типа даты, поэтому даты передаются как строки ISO 8601. JSON.stringify записывает их автоматически, но при разборе вы получаете обратно обычную строку — нужно самостоятельно воссоздать Date с помощью new Date(...) или ревивера (пример выше).
Обработка массивов записей
Ответы API обычно представляют собой массивы объектов. После разбора стандартные методы массива (forEach, map, filter) берут на себя остальное.
JSON и браузер: fetch и localStorage
Два места, где JSON встречается чаще всего:
fetch:response.json()читает тело ответа и разбирает его за вас, поэтому вы редко вызываетеJSON.parseнапрямую на результате fetch —const data = await response.json();. Смотрите руководство по Fetch API.localStorage: он хранит только строки, поэтому при сохранении используйте stringify, а при чтении — parse:localStorage.setItem('user', JSON.stringify(user)), затемJSON.parse(localStorage.getItem('user')).
Заключение
JSON — это формат, с которым вы будете работать чаще всего как JavaScript-разработчик, и всё сводится к двум методам: JSON.parse() для преобразования строки в значение, с которым можно работать, и JSON.stringify() для обратного преобразования значения в строку. Добавьте аргументы reviver и replacer для преобразования типов и фильтрации, отступы для читаемого вывода и toJSON для пользовательской сериализации — и вы сможете решить практически любую задачу обмена данными.
Помните о потерях при крайних случаях — пропускаемые undefined/функции, сериализованные даты и ошибки циклических ссылок — и используйте structuredClone(), когда вам нужна настоящая копия, а не JSON-преобразование. С этими инструментами вы уверенно сможете перемещать данные между приложением, сервером и хранилищем браузера.