tupleson
Version:
A hackable JSON serializer/deserializer
195 lines (156 loc) • 5.71 kB
Markdown
<h1 align="center">tupleSON</h1>
<p align="center">Your hackable JSON serializer/deserializer</p>
<p align="center">
<a href="#contributors" target="_blank">
<!-- prettier-ignore-start -->
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<img alt="All Contributors: 2" src="https://img.shields.io/badge/all_contributors-2-21bb42.svg" />
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<!-- prettier-ignore-end -->
</a>
<a href="https://codecov.io/gh/trpc/tupleson" target="_blank">
<img alt="Codecov Test Coverage" src="https://codecov.io/gh/trpc/tupleson/branch/main/graph/badge.svg"/>
</a>
<a href="https://github.com/trpc/tupleson/blob/main/.github/CODE_OF_CONDUCT.md" target="_blank">
<img alt="Contributor Covenant" src="https://img.shields.io/badge/code_of_conduct-enforced-21bb42" />
</a>
<a href="https://github.com/trpc/tupleson/blob/main/LICENSE.md" target="_blank">
<img alt="License: MIT" src="https://img.shields.io/github/license/trpc/tupleson?color=21bb42">
</a>
<img alt="Style: Prettier" src="https://img.shields.io/badge/style-prettier-21bb42.svg" />
<img alt="TypeScript: Strict" src="https://img.shields.io/badge/typescript-strict-21bb42.svg" />
</p>
---
Customizable serialization & deserialization.
Serialize almost[^1] anything!
[^1]:
🌀 Circular references not your thing? We agree & we don't support it.
But hey, feel free to make a PR add opt-in support for that if you need it!
### 🎯 Project Goals
- 💡 JSON-compatible output
- 📖 Human-readable output
- 🔧 Customizable behavior – tailor it to your exact needs
- 🌊 Serialize & stream things like `Promise`s or async iterators
> [!IMPORTANT]
> _Though well-tested, this package might undergo big changes and **does not** follow semver whilst on `0.x.y`-version, stay tuned!_
### 👀 Example
```ts
/* eslint-disable @typescript-eslint/no-unused-vars, n/no-missing-import */
import {
// Create serializer / deserializer
createTson,
// Serialize `bigint`
tsonBigint,
// Serialize `Date`
tsonDate,
// Serialize `Map`s
tsonMap,
// **throws** when encountering Infinity or NaN
tsonNumberGuard,
// Serialize regular expression
tsonRegExp,
// Serialize `Set`s
tsonSet,
// serialize a Symbol
tsonSymbol,
// Serialize `URL`s
tsonURL,
// Serialize `undefined`
tsonUndefined,
// **throws** when encountering non-registered complex objects (like class instances)
tsonUnknownObjectGuard,
} from "tupleson";
const tson = createTson({
/**
* The nonce function every time we start serializing a new object
* Should return a unique value every time it's called
* @default `${crypto.randomUUID} if available, otherwise a random string generated by Math.random`
*/
// nonce: () => "__tson",
types: [
// Pick which types you want to support
tsonSet,
],
});
const myObj = {
foo: "bar",
set: new Set([1, 2, 3]),
};
const str = tson.stringify(myObj, 2);
console.log(str);
// (👀 All non-JSON values are replaced with a tuple, hence the name)
// ->
// {
// "json": {
// "foo": "bar",
// "set": [
// "Set",
// [
// 1,
// 2,
// 3
// ],
// "__tson"
// ]
// },
// "nonce": "__tson"
// }
const obj = tson.parse(str);
// ✨ Retains type integrity
type Obj = typeof obj;
// ^?
// -> type Obj = { foo: string; set: Set<number>; }
```
### 🤯 Streaming `Promise`s and `AsyncIterable`s
- See example in [`./examples/async`](./examples/async)
- [Test it on StackBlitz](https://stackblitz.com/github/trpc/tupleson/tree/main/examples/async?file=src/app/page.tsx)
## 🧩 Extend with a custom serializer
> [!IMPORTANT]
> When defining custom serializers, the array order matters! If a value passes the test for more than one custom serializer, the first wins.
### ⌚️ [Temporal](https://www.npmjs.com/package/@js-temporal/polyfill)
> See test reference in [`./src/extend/temporal.test.ts`](./src/extend/temporal.test.ts)
```ts
/* eslint-disable @typescript-eslint/no-unused-vars, n/no-missing-import, n/no-unpublished-import */
import { Temporal } from "@js-temporal/polyfill";
import { TsonType, createTson } from "tupleson";
const plainDate: TsonType<Temporal.PlainDate, string> = {
deserialize: (v) => Temporal.PlainDate.from(v),
key: "PlainDate",
serialize: (v) => v.toJSON(),
test: (v) => v instanceof Temporal.PlainDate,
};
const instant: TsonType<Temporal.Instant, string> = {
deserialize: (v) => Temporal.Instant.from(v),
key: "Instant",
serialize: (v) => v.toJSON(),
test: (v) => v instanceof Temporal.Instant,
};
const tson = createTson({
types: [plainDate, instant],
});
```
### 🧮 [Decimal.js](https://github.com/MikeMcl/decimal.js)
> See test reference in [`./src/extend/decimal.test.ts`](./src/extend/decimal.test.ts)
```ts
/* eslint-disable @typescript-eslint/no-unused-vars, n/no-missing-import, n/no-unpublished-import */
import { Decimal } from "decimal.js";
const decimalJs: TsonType<Decimal, string> = {
deserialize: (v) => new Decimal(v),
key: "Decimal",
serialize: (v) => v.toJSON(),
test: (v) => v instanceof Decimal,
};
const tson = createTson({
types: [decimalJs],
});
```
---
<!-- ## All contributors ✨
<a href="https://github.com/trpc/tupleson/graphs/contributors">
<p align="center">
<img width="720" src="https://contrib.rocks/image?repo=trpc/tupleson" alt="A table of avatars from the project's contributors" />
</p>
</a> -->
<!-- spellchecker: enable -->
<!-- You can remove this notice if you don't want it 🙂 no worries! -->
> 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).