Skip to content

denostack/superserial

Repository files navigation

superserial

Build Coverage License Language Typescript
JSR version NPM Version Downloads

A comprehensive Serializer/Deserializer that can handle any data type.

Installation

Via JSR (Deno)

deno add jsr:@denostack/superserial
import { Superserial } from "@denostack/superserial";

Via NPM (Node.js, Bun, Deno)

# npm
npm install superserial

# bun
bun add superserial

# deno
deno add npm:superserial
import { Superserial } from "superserial";

Breaking Changes

Serialized Format Change

The serialized output format has been updated for performance optimization and introduces a breaking change when upgrading from 0.3.x to 0.4.x. Data serialized with superserial versions 0.3.x (or earlier) cannot be deserialized with version 0.4.x (or later).

Index

Built-in Objects

Value Properties

  • NaN
  • -0, +0
  • Infinity, -Infinity
  • undefined

Fundamental Objects

  • Symbol

ETC

  • BigInt
  • Date
  • RegExp
  • Map
  • Set
const superserial = new Superserial();

const symbol = Symbol();
const serialized = superserial.serialize({
  nzero: -0,
  und: undefined,
  nan: NaN,
  inf: Infinity,
  ninf: -Infinity,
  sym: symbol,
  bigint: 100n,
  date: new Date(),
  regex: /abc/gmi,
  map: new Map([["key1", "value1"], ["key2", "value2"]]),
  set: new Set([1, 2, 3, 4]),
});

const deserialized = superserial.deserialize(serialized);
/*
{
  nzero: -0,
  und: undefined,
  nan: NaN,
  inf: Infinity,
  ninf: -Infinity,
  sym: Symbol(), // Symbol but not exact same with original
  bigint: 100n,
  date: 2025-12-15T09:05:09.108Z, // ... Date!
  regex: /abc/gim,
  map: Map(2) { "key1" => "value1", "key2" => "value2" },
  set: Set(4) { 1, 2, 3, 4 }
}
*/

Circular Reference

Existing JSON functions do not support circular references, but superserial has solved this problem.

const nodes = [{ self: null as any, siblings: [] as any[] }, {
  self: null as any,
  siblings: [] as any[],
}];
nodes[0].self = nodes[0];
nodes[0].siblings = nodes;
nodes[1].self = nodes[1];
nodes[1].siblings = nodes;

const serialized = superserial.serialize(nodes);

console.log(serialized);
// [[[[0,1],[0,2]]],{"self":[0,1],"siblings":[0,0]},{"self":[0,2],"siblings":[0,0]}]

const deserialized = superserial.deserialize(serialized) as typeof nodes;

console.log(deserialized === deserialized[0].siblings); // true
console.log(deserialized[0] === deserialized[0].self); // true
console.log(deserialized === deserialized[1].siblings); // true
console.log(deserialized[1] === deserialized[1].self); // true

Circular Set & Map

const set = new Set();
set.add(set);

superserial.serialize(set); // [["Set",[0,0]]]

const map = new Map();
map.set(map, map);

superserial.serialize(map); // [["Map",[[[0,0],[0,0]]]]]

Deserialization also works perfectly!

const set = superserial.deserialize('[["Set",[0,0]]]') as Set<unknown>;

console.log(set === [...set][0]); // true

const map = superserial.deserialize('[["Map",[[[0,0],[0,0]]]]]') as Map<unknown, unknown>;

console.log(map === [...map.keys()][0]); // true
console.log(map === map.get([...map.keys()][0])); // true

Custom Class Support

Classes contain methods, getters, etc., but JSON doesn't fully support them. superserial allows you to preserve the class instance, including all its methods and internal state.

1. Using Decorators (Recommended)

The easiest way to register a class is using the @serializable decorator. You need to enable the decorator option when creating the Superserial instance.

import { serializable, Superserial } from "superserial";

@serializable()
class TestUser {
  constructor(public name: string, public age: number) {}

  greet() {
    return `Hello, ${this.name}`;
  }
}

// Enable decorator support
const superserial = new Superserial({ decorator: true });

const serialized = superserial.serialize(new TestUser("wan2land", 20));
console.log(serialized);
// [["TestUser",{"name":"wan2land","age":20}]]

const user = superserial.deserialize<TestUser>(serialized);
console.log(user instanceof TestUser); // true
console.log(user.greet()); // "Hello, wan2land"

You can also specify a custom name (alias) or custom serialization logic directly in the decorator.

@serializable("MyUser") // Alias
class TestUser {/* ... */}

@serializable({
  toSerialize: (user) => ({ n: user.name }), // Custom logic
  toDeserialize: (value) => new TestUser(value.n, 0),
})
class CompactUser {/* ... */}

2. Internal Control (Using Symbols)

If you need fine-grained control, such as handling private fields (#private) or transforming data, you can implement the toSerialize and toDeserialize symbols within your class. This keeps the serialization logic encapsulated within the class.

import { Superserial, toDeserialize, toSerialize } from "superserial";

class SecureUser {
  #age = 0;
  constructor(public name: string) {
    this.#age = 0;
  }

  setAge(age: number) {
    this.#age = age;
  }

  getAge() {
    return this.#age;
  }

  // Define what to save
  [toSerialize]() {
    return {
      name: this.name,
      age: this.#age, // Access private field
    };
  }

  // Define how to restore
  static [toDeserialize](value: { name: string; age: number }) {
    const user = new SecureUser(value.name);
    user.setAge(value.age);
    return user;
  }
}

const superserial = new Superserial({ classes: { SecureUser } });

const serialized = superserial.serialize(new SecureUser("Alice"));
// [["SecureUser",{"name":"Alice","age":0}]]

3. External Definition (Using defineClass)

If you are using a class from an external library and cannot modify its source code (e.g., adding decorators or symbols), you can inject the serialization logic using defineClass.

import { Superserial } from "superserial";

// Assume this is from a 3rd-party library
class ThirdPartyUser {
  constructor(public name: string, public age: number) {}
}

const superserial = new Superserial();

superserial.defineClass("ThirdPartyUser", {
  type: ThirdPartyUser,
  toSerialize(user: ThirdPartyUser) {
    return { n: user.name, a: user.age };
  },
  toDeserialize(value: { n: string; a: number }) {
    return new ThirdPartyUser(value.n, value.a);
  },
});

const serialized = superserial.serialize(new ThirdPartyUser("Bob", 30));
console.log(serialized);
// [["ThirdPartyUser",{"n":"Bob","a":30}]]

Manual Registration

If you don't use decorators, you can simply register classes via the classes option.

const serializer = new Superserial({
  classes: {
    TestUser,
    MyUser: User, // Register with an alias
  },
});

Benchmark

Please see benchmark results.

See also

About

A comprehensive Serializer/Deserializer that can handle any data type.

Topics

Resources

License

Stars

Watchers

Forks