UNPKG

tupleson

Version:

A hackable JSON serializer/deserializer

195 lines (156 loc) 5.71 kB
<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).