UNPKG

o2s2o

Version:

o2s2o: Object → Storage (JSON) → back to Object. Safe, readable de/serializer with class revival and built-ins.

130 lines (98 loc) 3.99 kB
# o2s2o _Object → Storage (JSON) → back to Object._ A tiny, readable de/serializer that **dehydrates** class instances into JSON-safe data and **hydrates** them back to real instanceswith support for built-ins (`Date`, `URL`, `RegExp` (flags kept), `Map`, `Set`, `Error`, `BigInt`). ### Why? - Keep your **class methods** after persisting to `localStorage`, DB, or sending over the wire. - No magic: uses **explicit envelopes** (`handlerId`, `ctorName`, `data`) for clarity. - **Deterministic** revival across ESM/CJS/minified builds using a **registry** or a `ctorMap`. ## Install ```bash npm i o2s2o # or yarn add o2s2o # or pnpm add o2s2o ``` ## Quick start ```ts import Serializer from 'o2s2o'; import type { AutoHandler } from 'o2s2o'; const S = new Serializer(); class Point { constructor(public x: number, public y: number) {} len() { return Math.hypot(this.x, this.y); } } // Register your classes (version ids recommended) S.register<Point>({ id: 'Point@1', ctor: Point }); const state = { p: new Point(3,4), when: new Date('2024-01-02T03:04:05Z') }; // Save const json = S.stringify(state); localStorage.setItem('app', json); // Restore const restored = S.parse<typeof state>(localStorage.getItem('app')!, { ctorMap: { Point } // deterministic mapping by constructor name }); restored.p instanceof Point; // true restored.p.len(); // 5 restored.when instanceof Date; // true ``` ## API ### `new Serializer()` ### `register<T>(handler: AutoHandler<T>)` Registers a class-type for auto dehydration/hydration. - `id`: stable string, versioned like `"Point@1"` - `ctor`: the class constructor - `keys?`: `(instance) => string[]` — which own keys to persist (default: `Object.keys(instance)`) - `construct?`: `(plain) => T` — custom builder on hydration (default: create a blank object with the prototype and `Object.assign` the hydrated fields) ### `dehydrateAny(value: any): any` Recursively converts any value to a JSON-safe shape. Class instances become envelopes: ```ts { handlerId: 'Point@1', data: { x: 3, y: 4 } } ``` Non-registered classes become: ```ts { ctorName: 'ClassName', data: { ... } } ``` Built-ins are handled out of the box. ### `hydrateAny(value: any, opts?: HydrationOptions): any` Rebuilds values back to live instances. Options: ```ts type HydrationOptions = { ctorMap?: Record<string, Constructor>; // deterministic ctor name -> ctor coerceScalars?: boolean; // turns "true","42","null" -> proper types (off by default) allowGlobalLookup?: boolean; // last-ditch globalThis lookup (off by default) }; ``` ### `stringify(obj: any): string` / `parse<T>(text: string, opts?: HydrationOptions): T` Friendly helpers around `JSON.stringify`/`JSON.parse` + (de)hydrate. ## Built-ins supported - `Date` ↔ ISO string - `URL` ↔ string - `RegExp` ↔ `{source, flags}` - `Map` ↔ array of `[key, value]` (both sides recursively processed) - `Set` ↔ array of values - `Error` ↔ `{name, message, stack}` - `BigInt` ↔ string ## Patterns ### Version your handlers ```ts S.register<Point>({ id: 'Point@2', ctor: Point, construct: (p) => new Point(p.x, p.y) }); ``` ### Enforce invariants ```ts class Money { constructor(public cents: number, public currency: 'USD'|'EUR') { if (!Number.isInteger(cents)) throw new Error('int'); } } S.register<Money>({ id: 'Money@1', ctor: Money, keys: m => ['cents','currency'], construct: p => new Money(p.cents, p.currency) }); ``` ### Deterministic hydration across bundles ```ts const restored = S.parse(json, { ctorMap: { Point, Money } }); ``` ## FAQ **Q: Does it support circular references?** No. JSON doesn’t either. You can layer an ID-based cycle encoder if needed. **Q: What about functions / symbols / `undefined`?** Same as JSON: they are dropped. **Q: Do I have to register built-ins?** No. They are included out of the box. ## License MIT © [Alireza Tabatabaeian](https://github.com/Alireza-Tabatabaeian)