Хранилище localStorage работает исключительно со
строками. Любое значение, которое сохраняется туда, автоматически
приводится к строковому виду. Это фундаментальное ограничение определяет
поведение всей системы хранения и является основной причиной потери
типов данных при работе с ним.
При сохранении объектов, чисел, булевых значений или сложных структур
разработчик неизбежно сталкивается с необходимостью сериализации. На
практике почти всегда используется JSON.stringify, а при
чтении — JSON.parse. Однако даже эта пара инструментов не
решает проблему полностью, поскольку JSON не поддерживает полный набор
типов JavaScript.
При записи данных происходит последовательность преобразований:
Пример:
localStorage.setItem("value", 42);
localStorage.getItem("value"); // "42"
Число 42 автоматически становится строкой
"42". Уже на этом этапе теряется исходный тип.
При использовании объектов процесс выглядит иначе:
const user = { name: "Alex", age: 30 };
localStorage.setItem("user", JSON.stringify(user));
const result = JSON.parse(localStorage.getItem("user"));
В этом случае структура сохраняется, но типовая информация — нет.
JSON поддерживает ограниченный набор типов:
nullВсе остальные типы приводятся к одному из этих представлений или теряются.
JSON.stringify({ a: undefined }); // "{}"
Ключ полностью исчезает из результата. Это приводит к
неоднозначности: отсутствие поля может означать как реальное отсутствие
данных, так и явное значение undefined.
JSON.stringify({
fn: function () { return 1; }
}); // "{}"
Функции полностью исключаются из сериализации. При восстановлении данных информация теряется без возможности восстановления.
JSON.stringify({ id: Symbol("x") }); // "{}"
Символы также игнорируются, так как не имеют JSON-представления.
Некоторые встроенные типы преобразуются, но теряют семантику.
const obj = { date: new Date() };
const json = JSON.stringify(obj);
Результат:
{"date":"2026-01-24T10:00:00.000Z"}
После восстановления:
const parsed = JSON.parse(json);
typeof parsed.date; // string
Дата превращается в строку, и для возврата исходного типа требуется дополнительная обработка:
parsed.date = new Date(parsed.date);
JSON.stringify({ a: NaN, b: Infinity });
// {"a":null,"b":null}
Эти значения приводятся к null, что делает невозможным
их различение после восстановления.
JSON.stringify({ id: 10n });
// TypeError: Do not know how to serialize a BigInt
BigInt не поддерживается JSON вовсе и требует ручной
сериализации:
JSON.stringify({ id: "10n" });
const map = new Map([["a", 1]]);
JSON.stringify(map); // "{}"
Map превращается в пустой объект, так как его внутренний формат не совместим с JSON.
const set = new Set([1, 2, 3]);
JSON.stringify(set); // "{}"
Аналогично, структура теряется.
Для восстановления требуется ручная сериализация:
const serialized = JSON.stringify([...set]);
const restored = new Set(JSON.parse(serialized));
Эти структуры вообще не сериализуются, поскольку не предоставляют возможности перечисления элементов.
При сериализации экземпляров классов сохраняются только поля объекта:
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hi ${this.name}`;
}
}
const user = new User("Alex");
const json = JSON.stringify(user);
const parsed = JSON.parse(json);
После восстановления:
User.prototypeparsed.greet; // undefined
Для восстановления требуется ручная реконструкция:
Object.setPrototypeOf(parsed, User.prototype);
Хотя JSON корректно хранит числа, возникают пограничные случаи:
JSON.stringify({ big: 99999999999999999 });
// "100000000000000000"
Основная проблема localStorage и JSON — отсутствие типовой информации. После восстановления невозможно отличить:
{
value: null
}
от:
{
value: undefined
}
или от отсутствующего поля:
{}
Все три случая могут привести к одному и тому же результату при неаккуратной обработке.
JSON.parse позволяет использовать функцию
восстановления:
JSON.parse(text, (key, value) => {
if (typeof value === "string" && value.endsWith("Z")) {
return new Date(value);
}
return value;
});
Это частично решает проблему, но требует:
Для минимизации потерь используют расширенные схемы сериализации:
const serialize = (value) => ({
__type: typeof value,
value
});
Пример:
const data = {
date: serialize(new Date().toISOString()),
set: serialize([...new Set([1, 2])])
};
Однако такой подход быстро усложняется при росте количества типов.
Потери типов — не просто техническая деталь, а архитектурное ограничение:
Это приводит к тому, что любое сложное состояние приложения требует дополнительного слоя абстракции.
localStorage.setItem("count", "10");
const count = localStorage.getItem("count") + 1; // "101"
if (localStorage.getItem("flag")) {
// true даже если "false"
}
localStorage.setItem("a", false);
localStorage.getItem("a") === false; // false
Потеря типизации приводит к более глубоким последствиям:
В результате работа с localStorage в сложных приложениях
превращается в ручное управление сериализацией на уровне приложения.
Часто применяются следующие стратегии:
localStorageТакие подходы позволяют частично компенсировать фундаментальные ограничения строкового хранилища и JSON-модели данных.