Сериализация данных через JSON

Хранение данных в браузере всегда связано с преобразованием структур JavaScript в формат, пригодный для долговременного хранения. В контексте localForage сериализация через JSON выступает как один из ключевых механизмов обеспечения совместимости, особенно при работе с драйвером localStorage, где возможны только строковые значения.

Сериализация определяет способ преобразования объектов, массивов, примитивов и сложных структур в строковое представление, а обратная десериализация восстанавливает исходное состояние данных при чтении из хранилища.

Место JSON в архитектуре localForage

localForage абстрагирует доступ к различным механизмам хранения:

  • IndexedDB
  • WebSQL (устаревший, но поддерживаемый в ряде окружений)
  • localStorage

Каждый из этих механизмов имеет собственные особенности хранения данных. Ключевая проблема заключается в том, что localStorage поддерживает только строковый формат. Именно здесь JSON становится универсальным решением.

При использовании localForage разработчик работает с объектами напрямую, однако внутри системы происходит преобразование данных в формат, совместимый с выбранным драйвером. В случае строкового хранилища используется JSON-сериализация.

Базовый механизм JSON-сериализации

JSON (JavaScript Object Notation) представляет собой текстовый формат обмена данными, основанный на подмножестве JavaScript-объектов.

Основные операции:

  • JSON.stringify(value) — преобразование объекта в строку
  • JSON.parse(string) — восстановление объекта из строки

Пример базового преобразования:

const state = {
  user: "alex",
  theme: "dark",
  count: 42
};

const serialized = JSON.stringify(state);
const restored = JSON.parse(serialized);

В контексте localForage аналогичная логика применяется при необходимости работы со строковыми драйверами.

Поведение localForage при сериализации

localForage скрывает детали сериализации, предоставляя единый API:

import localForage from "localforage";

await localForage.setItem("settings", {
  theme: "dark",
  fontSize: 16
});

const settings = await localForage.getItem("settings");

Внутренне происходят разные процессы в зависимости от драйвера:

  • IndexedDB — используется структурное клонирование (structured clone algorithm), JSON не требуется
  • localStorage — данные приводятся к строке, часто через JSON
  • WebSQL — хранение строк, сериализация зависит от реализации драйвера

Таким образом, JSON не является единственным механизмом, но играет ключевую роль в совместимости.

Ограничения JSON-сериализации

Несмотря на универсальность, JSON имеет ряд ограничений, которые критичны при работе с localForage.

Потеря типов данных

JSON поддерживает ограниченный набор типов:

  • string
  • number
  • boolean
  • null
  • object
  • array

Не поддерживаются напрямую:

  • Date
  • Map
  • Set
  • undefined
  • BigInt
  • функции

Пример проблемы с датой:

const data = {
  createdAt: new Date()
};

const serialized = JSON.stringify(data);
const parsed = JSON.parse(serialized);

parsed.createdAt instanceof Date; // false

Дата превращается в строку и требует ручного восстановления.

Способы обхода ограничений

Ручная сериализация сложных типов

Для восстановления типов используется постобработка:

await localForage.setItem("task", {
  title: "Report",
  createdAt: new Date().toISOString()
});

const task = await localForage.getItem("task");
task.createdAt = new Date(task.createdAt);

Использование replacer и reviver

JSON позволяет управлять сериализацией:

const serialized = JSON.stringify(obj, (key, value) => {
  if (value instanceof Date) {
    return { __type: "date", value: value.toISOString() };
  }
  return value;
});

const parsed = JSON.parse(serialized, (key, value) => {
  if (value && value.__type === "date") {
    return new Date(value.value);
  }
  return value;
});

Этот подход позволяет частично восстановить типизацию при работе с localForage через строковые драйверы.

Циклические структуры и JSON

JSON не поддерживает циклические ссылки. При попытке сериализации возникает ошибка:

const obj = {};
obj.self = obj;

JSON.stringify(obj); // TypeError

В контексте localForage это ограничение важно при хранении сложных графов данных.

Возможные решения:

  • устранение циклов перед сохранением
  • использование библиотек безопасной сериализации
  • преобразование структуры в плоский формат

Производительность сериализации

JSON-сериализация влияет на производительность при больших объемах данных.

Факторы:

  • размер объекта
  • глубина вложенности
  • частота операций setItem/getItem

Сложные объекты требуют значительных вычислительных затрат на:

  • преобразование в строку
  • парсинг обратно в объект

В IndexedDB этот процесс отсутствует, так как используется structured clone, что делает его более эффективным для больших структур.

localForage и structured clone vs JSON

Важно различать два подхода:

Structured Clone (IndexedDB)

  • поддерживает Map, Set, Date, ArrayBuffer
  • сохраняет типы данных
  • не требует JSON

JSON-сериализация (localStorage fallback)

  • работает только со строками
  • теряет типы
  • требует ручного восстановления

localForage автоматически выбирает стратегию в зависимости от доступного драйвера, что делает поведение непредсказуемым без учета окружения.

Хранение сложных объектов

При проектировании структуры данных необходимо учитывать различие между драйверами.

Пример сложного объекта:

const session = {
  id: 123,
  user: {
    name: "Alex",
    permissions: new Set(["read", "write"])
  },
  createdAt: new Date()
};

При сохранении:

  • IndexedDB сохранит объект корректно
  • localStorage приведет к JSON, потеряв Set и Date

Это создаёт необходимость нормализации данных перед сохранением.

Безопасность и JSON-сериализация

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

  • возможные неожиданные структуры
  • отсутствие валидации типов
  • риск поврежденных данных из хранилища

localForage не накладывает строгую схему, поэтому ответственность за целостность структуры лежит на прикладной логике.

Границы применимости JSON в localForage

JSON-сериализация эффективно работает в следующих сценариях:

  • хранение настроек пользователя
  • сохранение простых состояний UI
  • кэширование небольших структурированных данных

Менее эффективна при:

  • работе с бинарными данными
  • хранении сложных графов объектов
  • необходимости сохранения типов данных
  • высоконагруженных операциях записи/чтения

Поведение при ошибках сериализации

Ошибки JSON-сериализации могут возникать в момент сохранения:

  • циклические ссылки
  • BigInt значения
  • недопустимые типы

localForage в таких случаях может:

  • выбросить ошибку синхронно
  • отклонить Promise при setItem
  • потерять данные при неправильной обработке fallback-драйвера

Поэтому структура данных должна быть заранее подготовлена к сериализации.

Преобразование данных перед сохранением

Практика подготовки данных включает нормализацию:

  • удаление функций
  • преобразование Date в ISO строки
  • замена Map/Set на массивы
  • исключение undefined значений

Пример нормализации:

function normalize(data) {
  return {
    ...data,
    createdAt: data.createdAt?.toISOString?.() ?? null,
    tags: Array.from(data.tags || [])
  };
}

Такая подготовка снижает зависимость от конкретного драйвера localForage.

Итоговая роль JSON в экосистеме localForage

JSON выступает как универсальный слой совместимости между ограниченными строковыми хранилищами и объектной моделью JavaScript. Его использование особенно критично в сценариях, где доступен только localStorage, тогда как в IndexedDB сериализация фактически заменяется нативным механизмом structured clone.