Перейти к содержимому
Теория · Map, Set и JSON · урок 5 из 9

JSON

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

С объектом в программе та же история. Объект существует только внутри работающей программы. Чтобы сохранить его в файл или отправить на сервер, его превращают в строку. Функция String для этого не подходит:

JavaScript
const user = { name: "Ann", age: 30, admin: false };

console.log(String(user)); // [object Object]

const text = JSON.stringify(user);
console.log(text); // {"name":"Ann","age":30,"admin":false}
console.log(typeof text); // string

const copy = JSON.parse(text);
console.log(copy.name, copy.age); // Ann 30

Давайте разберём по шагам:

  1. String(user) даёт бесполезное [object Object]: свойства пропали.
  2. JSON.stringify(user) записывает все свойства в строку.
  3. typeof text подтверждает, что это обычная строка.
  4. JSON.parse(text) читает строку и собирает по ней новый объект copy.

Формат JSON

JSON (JavaScript Object Notation) — текстовый формат данных. Он похож на запись объекта в JavaScript, но строже: ключи и строки пишут только в двойных кавычках. Значениями бывают объекты, массивы, строки, числа, true, false и null.

Этот формат понимают почти все языки программирования, поэтому через него программы обмениваются данными. Две функции работают с ним в разные стороны:

  • JSON.stringify превращает значение в строку JSON;
  • JSON.parse делает обратное: читает строку и строит по ней новое значение, чаще всего объект.

Чтение строки JSON называют разбором. В примере copy — уже другой объект, и copy === user даёт false. Как и шкаф друга: он такой же, но не тот самый.

Что теряется

Здесь аналогия со шкафом перестаёт работать. Вы бы заметили, что какую-то деталь не получилось описать на листке. JSON.stringify такие детали пропускает молча.

В JSON нет undefined, функций, NaN и Infinity, и JSON.stringify без предупреждения пропускает или заменяет их:

  • свойство со значением undefined или с функцией пропадает из строки целиком;
  • NaN и Infinity записываются как null;
  • коллекции Map и Set превращаются в пустой объект {}.
JavaScript
const item = { id: 7, note: undefined, price: NaN, show() {} };
console.log(JSON.stringify(item)); // {"id":7,"price":null}

const tags = new Set(["js", "css"]);
console.log(JSON.stringify(tags)); // {}

Ошибки при этом нет, и пропажу вы заметите, только когда прочитаете данные обратно. Чтобы сохранить Map, коллекцию сначала превращают в массив пар: JSON.stringify([...map]).

Копия через JSON

Строка JSON никак не связана с исходным объектом, а JSON.parse строит все вложенные объекты заново. Поэтому JSON.parse(JSON.stringify(x)) даёт глубокую копию: копию, в которой и вложенные объекты новые. Но в ней будет только то, что умеет хранить JSON.

Функция structuredClone сохраняет undefined, NaN, Map и Set, но объект с методом скопировать не может:

JavaScript
const order = { id: 1, items: { pen: 2 }, note: undefined };
const viaJson = JSON.parse(JSON.stringify(order));
const clone = structuredClone(order);

order.items.pen = 5;
console.log(viaJson.items.pen, clone.items.pen); // 2 2
console.log("note" in viaJson, "note" in clone); // false true

Разберём, что здесь происходит:

  1. viaJson и clone — две глубокие копии order.
  2. order.items.pen = 5 меняет вложенный объект оригинала.
  3. Обе копии печатают 2: их вложенные объекты свои, не общие с order.
  4. Свойства note в viaJson нет, а в clone оно осталось.

На собеседовании часто спрашивают, как сделать глубокую копию объекта. Хороший ответ называет оба способа и что теряет каждый из них.

Частая ошибка

Строку для JSON.parse написали по правилам JavaScript, а не JSON:

JavaScript
console.log("start");
const settings = JSON.parse("{ size: 'M' }"); // ✗
console.log(settings.size);
TEXT
SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3)

Неверный текст не превращается в undefined: программа останавливается с ошибкой SyntaxError, и последний console.log уже не выполняется. Это текст ошибки в Node и Chrome, в других браузерах он звучит иначе.

Это ошибка выполнения, хотя и называется SyntaxError. Программа успевает напечатать start: JSON.parse проверяет текст только при вызове, и строки выше действительно выполнились.

Ключ без кавычек и одинарные кавычки допустимы в JavaScript, но не в JSON: верная строка — '{"size":"M"}'. Из-за таких строгих правил строки JSON обычно не пишут вручную, а получают из JSON.stringify.

Главное

  • JSON.stringify превращает значение в строку JSON, а JSON.parse строит по строке новое значение.
  • undefined, функции, NaN, Infinity, Map и Set теряются или заменяются без ошибки.
  • JSON.parse(JSON.stringify(x)) даёт глубокую копию только из того, что умеет хранить JSON; structuredClone сохраняет больше.
  • Неверная строка в JSON.parse бросает SyntaxError во время выполнения.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом