Методы JSON.stringify()
и JSON.parse()
служат для преобразования объекта в json и восстановления json в объект JS соответственно. Однако, они работают не со всеми элементами объекта корректно, а некоторые в целом игнорируются, что следует учитывать при копировании.
Здесь мы рассмотрим 2 момента, а именно невозможность перевода в JSON формат функций и символов, а так же ошибку/несоответствие при восстановлении данных на примере объекта типа Date
Введение
Возьмем объект со свойствами разных типов данных и методом:
const ticket = {
isValid: true, // boolean
price: 2000.0, // number
cityFrom: "MSK", // string
cityTo: "SPB", // string
date: new Date(), // Date()
*[Symbol.iterator]() {
for (const item of Object.entries(this)) yield item;
}, // function
};
console.dir(ticket);
// {
// isValid: true,
// price: 2000,
// cityFrom: 'MSK',
// cityTo: 'SPB',
// date: 2024-06-27T10:05:33.522Z,
// [Symbol(Symbol.iterator)]: [GeneratorFunction: [Symbol.iterator]]
// }
Так же нам для наглядности понадобиться функция проверки типов свойств. Для нашего объекта она выдает ожидаемые значения typeof
:
function testTypes(obj) {
for (const k in obj) console.log(k, typeof obj[k]);
}
testTypes(ticket);
// isValid boolean
// price number
// cityFrom string
// cityTo string
// date object
Стандартное преобразование
Обычно объект преобразуют напрямую, следующий код показывает сопутствующие моменты:
- При упаковке мы потеряли метод Symbol(Symbol.iterator);
- При восстановлении теряется тип данных даты, она становится строкой.
{
const json = JSON.stringify(ticket);
console.log(json);
// json = {"isValid":true,"price":2000,"cityFrom":"MSK","cityTo":"SPB","date":"2024-06-27T10:05:33.522Z"}
// Здесь мы встречаемся с первым моментом. Видно, что не все упаковалось, функцию мы уже потеряли.
const parsedObject = JSON.parse(json);
console.dir(parsedObject);
// {
// isValid: true,
// price: 2000,
// cityFrom: 'MSK',
// cityTo: 'SPB',
// date: '2024-06-27T10:05:33.522Z'
// }
testTypes(parsedObject);
// Если проверить типы, что мы увидим,
// что дата теперь не объект, а строка.
// isValid boolean
// price number
// cityFrom string
// cityTo string
// date string
}
JSON.stringify()
При упаковке, мы можем передать методу JSON.stringify
второй параметр в виде массива ключей, которые надо экспортировать в json.
const json = JSON.stringify(ticket, ["isValid", "price", "cityFrom", "date"]);
console.log("json:", json);
// json: {"isValid":true,"price":2000,"cityFrom":"MSK","date":"2024-06-27T11:18:08.648Z"}
// Проблема с датой осталась, но мы
// можем фильтровать нужные данные для передачи
JSON.parse()
С методом JSON.parse
все интересней, вторым аргументом можем передать функцию восстановления, с ее помощью мы сможем отслеживать интересующие нас поля и изменять их в процессе распаковки.
Пример функции восстановления для Date:
function R(k, v) {
if (
typeof v === "string" &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(v)
) {
return new Date(v);
}
return v;
}
Код с функцией восстановления будет выглядеть так:
{
const json = JSON.stringify(ticket, ["isValid", "price", "cityFrom", "date"]);
console.log("json:", json);
// json: {"isValid":true,"price":2000,"cityFrom":"MSK","date":"2024-06-27T11:18:08.648Z"}
// Проблема с датой осталась, но мы
// можем фильтровать нужные данные для передачи
// JSON.parse с функцией восстановления
const recoveredObject = JSON.parse(json, function (k, v) {
if (
typeof v === "string" &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(v)
) {
return new Date(v);
}
return v;
});
console.dir(recoveredObject);
// {
// isValid: true,
// price: 2000,
// cityFrom: 'MSK',
// date: 2024-06-27T11:24:24.112Z
// }
testTypes(recoveredObject);
// isValid boolean
// price number
// cityFrom string
// date object
}
Мы видим, что дата стала объектом, тем самым, который мы вернули в функции восстановления. Естественно ее можно расширить, для восстановления например собственных типов объекто, если у них реализован метод toJSON() и они свободно упаковываются методом JSON.stringify.
Итоговый код с комментариями
function testTypes(obj) {
for (const k in obj) console.log(k, typeof obj[k]);
}
const ticket = {
isValid: true, // boolean
price: 2000.0, // number
cityFrom: "MSK", // string
cityTo: "SPB", // string
date: new Date(), // Date()
*[Symbol.iterator]() {
for (const item of Object.entries(this)) yield item;
}, // function
};
console.dir(ticket);
// {
// isValid: true,
// price: 2000,
// cityFrom: 'MSK',
// cityTo: 'SPB',
// date: 2024-06-27T10:05:33.522Z,
// [Symbol(Symbol.iterator)]: [GeneratorFunction: [Symbol.iterator]]
// }
testTypes(ticket);
// isValid boolean
// price number
// cityFrom string
// cityTo string
// date object
{
const json = JSON.stringify(ticket);
console.log(json);
// json = {"isValid":true,"price":2000,"cityFrom":"MSK","cityTo":"SPB","date":"2024-06-27T10:05:33.522Z"}
// Здесь мы встречаемся с первым моментом. Видно, что не все упаковалось, функцию мы уже потеряли.
const parsedObject = JSON.parse(json);
console.dir(parsedObject);
// {
// isValid: true,
// price: 2000,
// cityFrom: 'MSK',
// cityTo: 'SPB',
// date: '2024-06-27T10:05:33.522Z'
// }
testTypes(parsedObject);
// Если проверить типы, что мы увидим, что дата теперь не объект, а строка.
// isValid boolean
// price number
// cityFrom string
// cityTo string
// date string
}
{
const json = JSON.stringify(ticket, ["isValid", "price", "cityFrom", "date"]);
console.log("json:", json);
// json: {"isValid":true,"price":2000,"cityFrom":"MSK","date":"2024-06-27T11:18:08.648Z"}
// Проблема с датой осталась, но мы
// можем фильтровать нужные данные для передачи
// JSON.parse с функцией восстановления
const recoveredObject = JSON.parse(json, function (k, v) {
if (
typeof v === "string" &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(v)
) {
return new Date(v);
}
return v;
});
console.dir(recoveredObject);
// {
// isValid: true,
// price: 2000,
// cityFrom: 'MSK',
// date: 2024-06-27T11:24:24.112Z
// }
testTypes(recoveredObject);
// isValid boolean
// price number
// cityFrom string
// date object
}