Skip to content

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

License

Notifications You must be signed in to change notification settings

denostack/superserial

Repository files navigation

superserial

Build Coverage License Language Typescript
JSR version Deno version NPM Version Downloads

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

Usage

with Deno

deno add @denostack/superserial
import { Serializer } from "@denostack/superserial";

const serializer = new Serializer();

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 = serializer.serialize(nodes);

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

with Node.js & Browser

Install

npm install superserial
import { Serializer } from "superserial";

// Usage is as above :-)

Index

Built-in Objects

Value Properties

  • NaN
  • Infinity, -Infinity
  • undefined
serializer.serialize({
  und: undefined,
  nan: NaN,
  inf: Infinity,
  ninf: -Infinity,
}); // {"und":undefined,"nan":NaN,"inf":Infinity,"ninf":-Infinity}

Fundamental Objects

  • Symbol

ETC

  • BigInt
  • Date
  • RegExp
  • Map
  • Set
const symbol = Symbol();
serializer.serialize({
  sym: symbol,
  bigint: 100n,
  date: new Date(),
  regex: /abc/gmi,
  map: new Map([["key1", "value1"], ["key2", "value2"]]),
  set: new Set([1, 2, 3, 4]),
});
// {"sym":$1,"bigint":100n,"date":$2,"regex":$3,"map":$4,"set":$5};Symbol();Date(1648740167514);/abc/gim;Map("key1"=>"value1","key2"=>"value2");Set(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 = serializer.serialize(nodes);

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

const deserialized = serializer.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);

serializer.serialize(set); // Set($0)

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

serializer.serialize(map); // Map($0=>$0)

Deserialization also works perfectly!

const set = serializer.deserialize("Set($0)");

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

const map = serializer.deserialize("Map($0=>$0)");

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

Class Support

Classes contain methods, getters, etc., but JSON doesn't fully support them. superserial includes features that make it easy to use.

The class to be used for deserialize is defined when the Serializer is created.

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

const serializer = new Serializer({ classes: { TestUser } });

Serializes the object and then deserializes it again. Since the original class object is converted as it is, all getters and methods can be used as they are.

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

const user = serializer.deserialize(serialized);
console.log(user); // TestUser { name: "wan2land", age: 20 }

Alias

If you want to serialize a class with a different name, you can use the classes option.

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

const serializer = new Serializer({
  classes: {
    AliasTestUser: TestUser,
  },
});
const serialized = serializer.serialize(new TestUser("wan2land", 20));
console.log(serialized);
// AliasTestUser{"name":"wan2land","age":20}   <--- AliasTestUser

const user = serializer.deserialize(serialized);
console.log(user); // TestUser { name: "wan2land", age: 20 }

toSerialize / toDeserialize

Private variables can be converted using two special symbols (toSerialize, toDeserialize).

When serializing(serialize), the object's data is created based on the toSerialize method. You can check the result of toSerialize by looking at the serialized string.

When deserializing(deserialize), it is impossible to create an object without a constructor call. (ref. No backdoor to access private) If the toDeserialize method is included, a value can be injected through toDeserialize after calling the constructor.

import {
  Serializer,
  toDeserialize,
  toSerialize,
} from "https://deno.land/x/superserial/mod.ts";

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

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

  getAge() {
    return this.#_age;
  }

  [toSerialize]() {
    return {
      name: this.name,
      age: this.#_age,
    };
  }

  [toDeserialize](
    value: {
      name: string;
      age: number;
    },
  ) {
    this.name = value.name;
    this.#_age = value.age;
  }
}

const serializer = new Serializer({ classes: { TestUser } });

{
  const user = new TestUser("wan2land");
  user.setAge(20);

  console.log(serializer.serialize(user)); // TestUser{"name":"wan2land","age":20}
}
{
  const user = serializer.deserialize<TestUser>(
    'TestUser{"name":"wan2land","age":20}',
  );
  console.log(user); // TestUser { name: "wan2land" }
  console.log(user.getAge()); // 20
}

Benchmark

Please see benchmark results.

See also