JSON в JavaScript: parse, stringify и подводные камни

Содержание

О проверке кода. Примеры не запускались — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».

JSON — формат обмена данными по умолчанию: API, конфиги, localStorage. В JavaScript для работы с ним есть два метода — parse и stringify. Разберём их и ловушки.

Два метода: parse и stringify

// объект → строка JSON (сериализация)
const user = { name: 'Аня', age: 30 };
const json = JSON.stringify(user);
// '{"name":"Аня","age":30}' — это СТРОКА

// строка JSON → объект (парсинг)
const obj = JSON.parse('{"name":"Аня","age":30}');
obj.name;   // 'Аня' — снова объект

Ключевое различие: JSON — это текст, объект — структура в памяти. stringify превращает объект в строку (для отправки по сети или записи в файл), parse — обратно. В JSON ключи всегда в двойных кавычках, нет функций, undefined, комментариев и висячих запятых.

Жизненный цикл данных через JSON
  1. 1 Объект в памяти{ name: ‘Аня’ } — с ним работает код
  2. 2 JSON.stringify→ строка ‘{“name”:“Аня”}’ для передачи/хранения
  3. 3 Сеть / файл / localStorageПередаётся или сохраняется как текст
  4. 4 JSON.parse→ снова объект на другой стороне

Форматирование с отступами

const data = { name: 'Аня', roles: ['admin', 'user'] };

JSON.stringify(data);          // '{"name":"Аня","roles":["admin","user"]}'

JSON.stringify(data, null, 2); // красиво, с отступами:
// {
//   "name": "Аня",
//   "roles": [
//     "admin",
//     "user"
//   ]
// }

Третий аргумент stringify — размер отступа. JSON.stringify(obj, null, 2) добавляет переносы и отступы в 2 пробела — незаменимо для логов, файлов конфигурации и отладки. Второй аргумент (null здесь) — фильтр полей, о нём ниже.

Что теряется при сериализации

const obj = {
  name: 'Аня',
  greet() {},                 // функция
  role: undefined,            // undefined
  created: new Date(),        // Date
  tags: new Set([1, 2]),      // Set
};

JSON.parse(JSON.stringify(obj));
// { name: 'Аня', created: '2026-07-17T...', tags: {} }
// greet и role ИСЧЕЗЛИ, created стал строкой, tags стал пустым объектом!

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

  • функции, undefined, символы — молча пропадают;
  • Date — превращается в строку (обратно в Date сам не станет);
  • Map, Set — становятся пустыми {};
  • BigInt — вызывает ошибку.

Это главная причина багов: сохранили объект с датой, прочитали — а там строка. При parse даты нужно восстанавливать вручную.

Обработка ошибок парсинга

// ❌ невалидный JSON роняет программу
JSON.parse("{ name: 'Аня' }");   // SyntaxError — одинарные кавычки, ключ без кавычек

// ✅ внешние данные — всегда в try/catch
function safeParse(str) {
  try {
    return JSON.parse(str);
  } catch (e) {
    console.error('Невалидный JSON:', e.message);
    return null;
  }
}

JSON.parse бросает SyntaxError на любой невалидной строке — лишняя запятая, одинарные кавычки, ключ без кавычек. Данные из сети, от пользователя или из файла нельзя считать корректными, поэтому parse всегда оборачивают в try/catch. Пропустить эту проверку — значит подписаться на случайный краш.

Глубокая копия: старый трюк и замена

const original = { user: { name: 'Аня' } };

// старый трюк глубокой копии
const copy1 = JSON.parse(JSON.stringify(original));
copy1.user.name = 'Борис';   // оригинал цел

// современный способ
const copy2 = structuredClone(original);

Долгое время JSON.parse(JSON.stringify(obj)) был народным способом сделать глубокую копию объекта. Он работает для плоских данных, но теряет функции, даты и специальные типы — те же ограничения сериализации. Сейчас для этого есть structuredClone(obj), который копирует корректно, включая Date, Map, Set. Трюк с JSON остаётся разве что для совсем простых объектов.

Фильтрация и трансформация

const user = { name: 'Аня', password: '123', age: 30 };

// убрать поле при сериализации (второй аргумент — массив разрешённых ключей)
JSON.stringify(user, ['name', 'age']);   // '{"name":"Аня","age":30}' — без пароля

// или функция-заменитель
JSON.stringify(user, (key, value) =>
  key === 'password' ? undefined : value
);

Второй аргумент stringify позволяет отфильтровать поля — например, убрать пароль перед логированием. Можно передать массив разрешённых ключей или функцию-заменитель. У parse есть парный аргумент-ревайвер для восстановления типов (например, строк обратно в Date).


Смежные темы: примитивы и ссылки — глубокая копия; обработка ошибок — try/catch вокруг parse; localStorage — хранение через JSON. Полный список — в уроках JavaScript.

Частые вопросы

Чем JSON отличается от объекта JavaScript?
JSON — это текстовый формат (строка), а объект — структура в памяти. JSON.parse превращает строку в объект, JSON.stringify — объект в строку. В JSON ключи всегда в двойных кавычках и нет функций, undefined, комментариев.
Что теряется при JSON.stringify?
Функции, undefined и символы пропускаются. Даты превращаются в строки, Map и Set — в пустые объекты, а BigInt вызывает ошибку. При обратном parse строка-дата не станет объектом Date автоматически.
Как красиво отформатировать JSON?
Третьим аргументом JSON.stringify передают число отступа: JSON.stringify(obj, null, 2). Это добавит переносы строк и отступы в 2 пробела — удобно для логов и файлов конфигурации.
Почему JSON.parse кидает ошибку?
Если строка не является валидным JSON — лишняя запятая, одинарные кавычки, ключ без кавычек. Поэтому JSON.parse всегда оборачивают в try/catch при работе с внешними данными, которым нельзя доверять.
Можно ли копировать объект через JSON?
JSON.parse(JSON.stringify(obj)) делает глубокую копию, но теряет функции, даты и специальные типы. Сейчас для этого есть structuredClone, который копирует корректно. Трюк с JSON остаётся для простых плоских данных.