Сериализация: JSON, V8, BSON
Преобразование объектов в строку/байты для передачи и хранения. JSON универсален, но не сохраняет классы, функции, символы, Date. Бинарные форматы (BSON, v8) плотнее и сохраняют больше.
Что это
Сериализация — превращение объекта в памяти (структура с указателями) в линейный формат, который можно сохранить или передать. Десериализация — обратное. Делятся на текстовые (CSV, JSON, XML, YAML — читаемые) и бинарные (BSON, v8.serialize, protobuf — плотные, нечитаемые).
В JSON у каждого числа разная длина (1 vs 100), в бинарном — одинаковая. Бинарный формат компактнее в одном, тяжелее в другом.
JSON и его ограничения
JSON.stringify({ a: 1, b: undefined, c: Infinity, d: NaN, e: null });
// '{"a":1,"c":null,"d":null,"e":null}'
// undefined пропадает, Infinity и NaN становятся null
Что теряется при JSON.stringify:
undefined— выпадает из ключей и массивовSymbol— игнорируетсяInfinity,NaN— становятсяnullDate— превращается в строку черезtoString, обратно вDateсам не парсится- Классы:
new Aиnew Bсериализуются одинаково — информации о классе нет - Функции — пропадают
Бинарная сериализация V8
const v8 = require('node:v8');
const obj = { date: new Date, big: 9999n, set: new Set([1, 2, 3]) };
const buf = v8.serialize(obj); // Buffer
const back = v8.deserialize(buf); // объект восстановлен
// v8 сохраняет: Date, BigInt, Map, Set, RegExp, TypedArray
v8.serialize использует внутренний формат V8 (HostObject protocol) — это байндинг к встроенным библиотекам. Не путать с vm — vm.createScript исполняет JS-код, а v8 сериализует значения.
Свой расширяемый сериализатор
// Коллекция: тип → функция сериализации
const serializers = {
null: => 'null',
string: (v) => JSON.stringify(v),
number: (v) => v.toString(),
boolean: (v) => v.toString(),
function: (v) => v.toString(), // toString функции возвращает её код
array: (v) => `[${v.map(serialize).join(',')}]`,
object: (v) => {
const pairs = Object.entries(v).map(([k, val]) => `${k}:${serialize(val)}`);
const symbols = Object.getOwnPropertySymbols(v)
.map(s => `${s.toString()}:${serialize(v[s])}`);
return `{${[...pairs, ...symbols].join(',')}}`;
},
};
function serialize(obj) {
if (obj === null) return serializers.null;
const type = Array.isArray(obj) ? 'array' : typeof obj;
return serializers[type](obj);
}
// Десериализация — через vm.createScript (свой формат — это валидный JavaScript)
const vm = require('node:vm');
const live = new vm.Script(`(${serialized})`).runInThisContext;
Подводные камни
- JSON не для бизнес-логики с типами: Date, классы, Map/Set теряются — нужны DTO с явным парсингом
JSON.stringify(circular)—TypeError. Нуженutil.inspectили собственный обход с WeakSet- CSV не split-ом: внутри ячейки могут быть кавычки и запятые с escape — простой split ломается
v8.serializeне кроссплатформенный между мажорными версиями V8 — не для долговременного хранилища
🎓 Источники
- 🎓 Сериализация и десериализация в JavaScript и Node.js · 2018-11-26
- Тезисы:
- В JSON
1— длина 1,100— длина 3. В бинарном — одинаковая длина. Уплотняет в одном, увеличивает в другом console.dirтоже сериализатор: для вывода объекта нужно превратить указатель в строку- Свой формат может сериализовать функции через
function.toString()(для native — пометка native code) - Расширяемый сериализатор: справочник «тип → функция сериализации»; главная функция становится маленькой
- Десериализация своего формата через
vm.createScript: формат — это валидный JS, можем исполнить v8.serialize/deserialize— байндинг на встроенные библиотеки V8 (не путать сvm)- Для символов нужен отдельный цикл по
Object.getOwnPropertySymbols
- В JSON
- Цитата:
«Тут получается непрямая рекурсия, потому что внутри каждого сериализаторов может опять лежать serialize. Две функции, одна другую вызывает.»
- Тезисы: