Хранение JSON: когда нужна ручная сериализация

idb-keyval — это легковесная библиотека для работы с IndexedDB в браузере, предоставляющая удобный API для хранения и получения данных. Одной из ключевых особенностей является возможность работы с объектами и примитивами без необходимости вручную сериализовать и десериализовать данные, однако при работе с JSON иногда требуется ручная сериализация.

Когда требуется ручная сериализация

IndexedDB и idb-keyval умеют хранить объекты JavaScript напрямую, но есть ограничения:

  • Циклические ссылки в объектах приводят к ошибкам при автоматическом сохранении.
  • Некоторые типы данных, такие как Map, Set, Date и функции, не сохраняются корректно без преобразования.
  • Требуется контроль формата данных при совместном использовании с другими сервисами или API, ожидающими строку JSON.

В этих случаях использование JSON.stringify и JSON.parse становится необходимым для ручного управления сериализацией.

import { set, get } from 'idb-keyval';

const user = {
  name: 'Alice',
  lastLogin: new Date(),
  preferences: new Map([['theme', 'dark']])
};

// Преобразование объекта в JSON-строку перед сохранением
set('user', JSON.stringify(user));

Чтение и десериализация JSON

При получении данных из хранилища важно учитывать, что они теперь представлены как строка JSON, а не объект JavaScript. Для восстановления структуры применяется JSON.parse.

get('user').then(jsonString => {
  if (jsonString) {
    const user = JSON.parse(jsonString);
    console.log(user.name); // 'Alice'
  }
});

Работа с вложенными структурами

Сложные вложенные объекты могут содержать типы данных, не поддерживаемые IndexedDB напрямую. Например, Date и Map теряют свою семантику при автоматическом хранении:

const data = {
  created: new Date(),
  tags: new Set(['javascript', 'idb-keyval'])
};

// JSON.stringify корректно сериализует объекты
set('data', JSON.stringify({
  ...data,
  created: data.created.toISOString(),
  tags: Array.from(data.tags)
}));

// Восстановление
get('data').then(jsonString => {
  const parsed = JSON.parse(jsonString);
  parsed.created = new Date(parsed.created);
  parsed.tags = new Set(parsed.tags);
});

Ключевые моменты:

  • Использование Array.from для Set и Object.fromEntries для Map позволяет сохранить семантику коллекций при сериализации.
  • Даты преобразуются в строки ISO и обратно в объекты Date после десериализации.

Автоматизация сериализации и десериализации

Чтобы не писать повторяющийся код для каждой структуры, можно создать обёртки для set и get:

function setJSON(key, value) {
  return set(key, JSON.stringify(value));
}

function getJSON(key) {
  return get(key).then(value => value ? JSON.parse(value) : undefined);
}

// Пример использования
setJSON('settings', { theme: 'dark', volume: 80 });
getJSON('settings').then(settings => console.log(settings.theme));

Преимущества ручной сериализации

  • Полный контроль над форматом данных.
  • Возможность добавления дополнительных метаданных при сохранении.
  • Совместимость с внешними API, где требуется именно JSON-строка.
  • Возможность безопасно хранить сложные структуры, включая циклические зависимости (с помощью специальных библиотек сериализации).

Рекомендации по использованию

  1. Сохранять JSON-строки для объектов с нестандартными типами данных (Map, Set, Date).
  2. Использовать обёртки для повторяющихся операций сериализации и десериализации.
  3. Проверять данные перед парсингом, чтобы избежать ошибок при поврежденных строках JSON.
  4. Для простых объектов и массивов, не содержащих нестандартные типы, можно использовать стандартные методы idb-keyval без ручной сериализации.

Заключение по подходу

Ручная сериализация JSON в idb-keyval необходима, когда данные выходят за рамки простых объектов и массивов, требуют сохранения специальных типов или контроля формата. Такой подход обеспечивает гибкость, безопасность и совместимость с внешними системами, одновременно позволяя максимально эффективно использовать возможности IndexedDB.