Skip to content
UniKVS
English
Esc
↑↓navigate↵open⌘Jpreview
On this page

@unikvs/superjson

Explains how to use the Transformer plugin that serializes values to JSON while preserving Date, Map, Set, BigInt, undefined, and circular references.

Overview

@unikvs/superjson is a Transformer plugin that serializes arbitrary values with SuperJSON. Transformer It wraps SuperJSON to fit ITransformer.

Converting to plain JSON turns Date into a string, Map and Set into empty objects, and fails to serialize BigInt. @unikvs/superjson stores type information alongside the data, so these values round-trip with their types intact.

The storage format is based on JSON, so you can inspect the contents as text. If plain JSON is enough, consider @unikvs/json; if you want a smaller binary format, consider @unikvs/cbor.

This transformer is always open. No explicit open or close is needed; isOpen always returns true and name is always "Superjson".

Install

Install it as follows.

npm install @unikvs/superjson
pnpm add @unikvs/superjson
yarn add @unikvs/superjson
bun add @unikvs/superjson
nub add @unikvs/superjson
aube add @unikvs/superjson

Supported Types

The following values round-trip with their type information preserved.

Type Example Notes
Date new Date() The instant is preserved.
Map new Map([["a", 1]]) Both keys and values are preserved.
Set new Set([1, 2]) The element order is also preserved.
BigInt 10n
undefined undefined Preserved both at the root and nested.
Circular references obj.self = obj The reference relationships are preserved.
Special numbers NaN, Infinity, -Infinity, -0 Values that plain JSON turns into null are preserved.
Typed arrays Uint8Array, Float64Array, and others
RegExp, URL, Error Only the main properties are preserved.
Arrays and objects Primitives and strings are preserved as is.

The following values are not preserved.

Value Result
Functions and symbols (nested) Removed together with their properties.
Functions and symbols (root) Throws SuperjsonUnsupportedValueError.
Class instances Become plain objects and lose their prototype.

Usage

The Superjson constructor takes no arguments.

import { Superjson } from "@unikvs/superjson";

const superjson = new Superjson();

Its main members are as follows.

Member Summary
new Superjson() Initializes with no arguments.
name Always returns "Superjson".
isOpen Always returns true.
encode({ data }) Converts an arbitrary value into a UTF-8 byte sequence of a SuperJSON payload.
decode({ data }) Restores a value from a UTF-8 byte sequence of a SuperJSON payload.
getEncodable() Returns a TransformStream that converts values into JSON Lines byte sequences.
getDecodable() Returns a TransformStream that restores values from JSON Lines byte sequences.

Register it with UniKvs via appendTransformer. It becomes the upstream of subsequently registered storages.

import { Superjson } from "@unikvs/superjson";
import { Memory } from "@unikvs/memory";
import UniKvs, { type PlainValue } from "unikvs";

type Profile = {
  name: string;
  joinedAt: Date;
  tags: Set<string>;
};

const kvs = UniKvs.config<{ profile: PlainValue<Profile> }>()
  .appendTransformer(new Superjson())
  .appendStorage(new Memory())
  .create();

await kvs.open();

await kvs.set("profile", {
  name: "tai-kun",
  joinedAt: new Date("2024-01-02T03:04:05.678Z"),
  tags: new Set(["typescript", "kvs"]),
});

const profile = await kvs.get("profile");
console.log(profile.joinedAt instanceof Date);
console.log(profile.tags instanceof Set);

set serializes and stores the value, and get restores it automatically.

Streams

getEncodable and getDecodable return TransformStreams that convert between values and byte sequences.

getEncodable treats each chunk as one SuperJSON payload and appends a newline (\n) at the end of the line. The output is JSON Lines (JSONL / NDJSON). Even when a value contains a newline character, SuperJSON escapes it as a JSON string, so it does not collide with the line boundaries.

{"json":"2024-01-02T03:04:05.678Z","meta":{"values":["Date"],"v":1}}
{"json":[["a",1]],"meta":{"values":["map"],"v":1}}

getDecodable reads JSON Lines with the following rules.

  • It restores one value per newline (\n).
  • It strips the \r of CRLF (\r\n).
  • It ignores empty lines.
  • A trailing newline is optional. Without one, it processes the last line as is.
  • Chunk boundaries can be arbitrary. It handles one byte at a time and multi-byte UTF-8 characters split across chunks correctly.
  • An empty stream completes without emitting any values.
const source = new ReadableStream<unknown>({
  start(controller) {
    controller.enqueue(new Date());
    controller.enqueue(new Map([["a", 1]]));
    controller.close();
  },
});

const decoded = source
  .pipeThrough(superjson.getEncodable())
  .pipeThrough(superjson.getDecodable());

for await (const value of decoded) {
  console.log(value);
}

A stream containing an invalid line or invalid UTF-8 is rejected. See Errors for the error types.

Errors

This package throws the following error for a root value it cannot serialize.

Error Meaning Resolution
SuperjsonUnsupportedValueError The root value is a function or a symbol. meta.type is "function" or "symbol". Convert the value so that it contains no functions or symbols, then store it.
import { Superjson, SuperjsonUnsupportedValueError } from "@unikvs/superjson";

const superjson = new Superjson();

try {
  superjson.encode({ data: () => 1 });
} catch (error) {
  if (error instanceof SuperjsonUnsupportedValueError) {
    console.log(error.name);
    console.log(error.meta.type);
  } else {
    throw error;
  }
}

decode and getDecodable also throw native errors for corrupted payloads.

Situation Error
Empty bytes or a line that cannot be parsed as JSON SyntaxError
Bytes that cannot be decoded as UTF-8 TypeError

Examples

A minimal example with Memory storage. Written values are stored as SuperJSON payloads and automatically restored on reads.

import { Superjson } from "@unikvs/superjson";
import { Memory } from "@unikvs/memory";
import UniKvs, { type PlainValue } from "unikvs";

type Snapshot = {
  createdAt: Date;
  counts: Map<string, number>;
  flags: Set<string>;
  revision: bigint;
  note: string | undefined;
};

const kvs = UniKvs.config<{ snapshot: PlainValue<Snapshot> }>()
  .appendTransformer(new Superjson())
  .appendStorage(new Memory())
  .create();

await kvs.open();

const snapshot: Snapshot = {
  createdAt: new Date("2024-01-02T03:04:05.678Z"),
  counts: new Map([
    ["read", 1],
    ["write", 2],
  ]),
  flags: new Set(["dirty"]),
  revision: 1n,
  note: undefined,
};

await kvs.set("snapshot", snapshot);

const restored = await kvs.get("snapshot");
console.log(restored.createdAt instanceof Date);
console.log(restored.counts instanceof Map);
console.log(restored.flags instanceof Set);
console.log(typeof restored.revision);
console.log("note" in restored);

await kvs.close();

Was this page helpful?