JSON
Представьте, что вы переезжаете и хотите передать другу шкаф по почте. Шкаф целиком в конверт не влезет. Зато можно записать на листке, из каких досок он собран, а друг по записи соберёт такой же шкаф у себя.
С объектом в программе та же история. Объект существует только внутри работающей программы. Чтобы сохранить его в файл или отправить на сервер, его превращают в строку. Функция String для этого не подходит:
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Давайте разберём по шагам:
String(user)даёт бесполезное[object Object]: свойства пропали.JSON.stringify(user)записывает все свойства в строку.typeof textподтверждает, что это обычная строка.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превращаются в пустой объект{}.
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, но объект с методом скопировать не может:
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Разберём, что здесь происходит:
viaJsonиclone— две глубокие копииorder.order.items.pen = 5меняет вложенный объект оригинала.- Обе копии печатают
2: их вложенные объекты свои, не общие сorder. - Свойства
noteвviaJsonнет, а вcloneоно осталось.
На собеседовании часто спрашивают, как сделать глубокую копию объекта. Хороший ответ называет оба способа и что теряет каждый из них.
Частая ошибка
Строку для JSON.parse написали по правилам JavaScript, а не JSON:
console.log("start");
const settings = JSON.parse("{ size: 'M' }"); // ✗
console.log(settings.size);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 и подпишись на канал — откроются все уроки.