Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/skarab42/tson
Type Safe Object Notation & Validation
https://github.com/skarab42/tson
javascript json schema serialization type-safe type-safety typescript validation
Last synced: 15 days ago
JSON representation
Type Safe Object Notation & Validation
- Host: GitHub
- URL: https://github.com/skarab42/tson
- Owner: skarab42
- License: mit
- Created: 2022-03-12T15:28:33.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2023-10-08T10:13:35.000Z (about 1 year ago)
- Last Synced: 2024-04-29T16:21:18.113Z (7 months ago)
- Topics: javascript, json, schema, serialization, type-safe, type-safety, typescript, validation
- Language: TypeScript
- Homepage:
- Size: 435 KB
- Stars: 10
- Watchers: 3
- Forks: 1
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# tson
**Type Safe Object Notation & Validation**
[![Test and Lint](https://github.com/skarab42/tson/actions/workflows/CI.yaml/badge.svg)](https://github.com/skarab42/tson/actions/workflows/CI.yaml) [![codecov](https://codecov.io/gh/skarab42/tson/branch/main/graph/badge.svg?token=4PSFJBVAFB)](https://codecov.io/gh/skarab42/tson) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/skarab42/tson?color=success&style=flat) ![GitHub](https://img.shields.io/github/license/skarab42/tson?color=success) [![GitHub Sponsors](https://img.shields.io/github/sponsors/skarab42?color=ff69b4&label=%E2%9D%A4%20sponsors%20)](https://github.com/sponsors/skarab42) [![Twitch Status](https://img.shields.io/twitch/status/skarab42?style=social)](https://www.twitch.tv/skarab42)
📌 Work in Progress, not ready for production...
## Features
- 🧱 Functional
- 🔷 Immutable
- ✅ Well tested## Why?
After a contribution to the [tRPC](https://github.com/trpc/trpc) project, I wanted to understand more deeply the use of generics and inference in TypeScript.
I needed a challenge so I set myself the goal of coding my own schema validation library.
This library is heavily inspired by [Zod](https://github.com/colinhacks/zod) (_I try to provide the same API_) but in order to avoid cloning it, I challenged myself to not use any classes.# Install
```bash
pnpm add @skarab/tson
```_`yarn` and `npm` also works_
## ES and CommonJS module
```ts
import { t } from "tson";
``````ts
const { t } = require("tson");
```# Examples
```ts
import { t } from "tson";const name = t.string();
name.parse("nyan"); // return "nyan"
name.parse(42); // throw TypeCheckError
``````ts
import { t } from "tson";const user = t.object({
name: t.string(),
age: t.number(),
admin: t.boolean(),
});user.parse({ name: "nyan", age: 42, admin: true });
type User = t.infer;
// { name: string, age: number, admin: boolean }
```# Strict mode
## TypeScript
It is strongly recommended to activate the [strict](https://www.typescriptlang.org/tsconfig#strict) mode of TypeScript which will activate all checking behaviours that results in stronger guarantees of the program's correctness.
## tson
By default `tson` parse objects in `STRICT` mode, this means that all undefined values in a scheme will be considered as an error. You can change this behaviour globally or locally, the procedure is documented [here](#objectschema-mode).
# Table of contents
- [tson](#tson)
- [Features](#features)
- [Why?](#why)
- [Install](#install)
- [ES and CommonJS module](#es-and-commonjs-module)
- [Examples](#examples)
- [Strict mode](#strict-mode)
- [TypeScript](#typescript)
- [tson](#tson-1)
- [Table of contents](#table-of-contents)
- [API](#api)
- [First level types](#first-level-types)
- [Primitive types](#primitive-types)
- [Numbers types](#numbers-types)
- [Empty types](#empty-types)
- [Catch-all types](#catch-all-types)
- [Never type](#never-type)
- [literal(value)](#literalvalue)
- [array(type)](#arraytype)
- [tuple(...type)](#tupletype)
- [tuple(type[])](#tupletype-1)
- [tuple(type[] as const)](#tupletype-as-const)
- [object(schema)](#objectschema)
- [object(schema, mode)](#objectschema-mode)
- [object helpers](#object-helpers)
- [.strict()](#strict)
- [.strip()](#strip)
- [.passthrough()](#passthrough)
- [union(...type)](#uniontype)
- [union(type[])](#uniontype-1)
- [union(type[] as const)](#uniontype-as-const)
- [optional(type)](#optionaltype)
- [enum(...string)](#enumstring)
- [Access enum properties](#access-enum-properties)
- [Access enum values](#access-enum-values)
- [Test enum values](#test-enum-values)
- [Infer enum type](#infer-enum-type)
- [enum(string[])](#enumstring-1)
- [enum(string[] as const)](#enumstring-as-const)
- [enum(object)](#enumobject)
- [enum(object as const)](#enumobject-as-const)
- [enum(enum)](#enumenum)
- [nativeEnum(enum)](#nativeenumenum)
- [instanceof(type)](#instanceoftype)
- [date()](#date)
- [record(type)](#recordtype)
- [set(type)](#settype)
- [set(...type)](#settype-1)
- [set([type, ...type])](#settype-type)
- [map(keyType, valueType)](#mapkeytype-valuetype)
- [map(schema)](#mapschema)
- [promise(type)](#promisetype)
- [function()](#function)
- [function(args)](#functionargs)
- [function(args, returns)](#functionargs-returns)
- [function(args, returns, implement)](#functionargs-returns-implement)
- [preprocess(filter, type)](#preprocessfilter-type)
- [postprocess(filter, type)](#postprocessfilter-type)
- [postprocess(filter, inputType, outputType)](#postprocessfilter-inputtype-outputtype)
- [Type helpers](#type-helpers)
- [safeParse(input)](#safeparseinput)
- [optional()](#optional)
- [preprocess()](#preprocess)
- [postprocess()](#postprocess)
- [Contributing 💜](#contributing-)# API
## First level types
### Primitive types
```ts
t.string();
t.number();
t.bigint();
t.boolean();
t.symbol();
t.date();
```### Numbers types
```ts
t.nan();
t.finite();
t.infinity();
t.integer(); // Alias: int()
t.unsignedNumber(); // Alias: unumber()
t.unsignedInteger(); // Alias: uinteger(), uint()
```### Empty types
```ts
t.undefined();
t.null();
t.void();
```### Catch-all types
```ts
t.any();
t.unknown();
```### Never type
```ts
t.never();
```## literal(value)
```ts
const life = t.literal(42);
const love = t.literal(true);
const name = t.literal("nyan");life.value; // type => 42
```## array(type)
```ts
const arr1 = t.array(t.string()); // string[]
const arr2 = t.array(t.boolean()); // boolean[]
```## tuple(...type)
```ts
const tpl = t.tuple(t.string(), t.number(), t.string()); // [string, number, string]
```## tuple(type[])
```ts
const tpl = t.tuple([t.string(), t.number(), t.string()]); // [string, number, string]
```💔 The following code does not work, TypeScript can not infer array values properly. Use the `as const` workaround to do this.
```ts
const types = [t.string(), t.number(), t.string()];
const tpl = t.tuple(types); // [string, number, string]
```## tuple(type[] as const)
```ts
const types = [t.string(), t.number(), t.string()] as const;
const tpl = t.tuple(types); // [string, number, string]
```## object(schema)
```ts
const user = t.object({
name: t.string(),
age: t.number(),
admin: t.boolean(),
});type User = t.infer;
// { name: string, age: number, admin: boolean }
```## object(schema, mode)
By default `tson` parse objects in `STRICT` mode, but you can change the mode globally or locally.
There are three modes:
- `STRICT`: Will raise an error if a key is not defined in the schema.
- `STRIP`: Strips undefined keys from the result and does not raise an error.
- `PASSTHROUGH`: Keeps undefined keys and does not raise an error.Change the default mode globally.
```ts
t.defaultSettings.objectTypeMode = t.ObjectTypeMode.STRIP;
```Change the mode locally.
```ts
const schema = { a: t.string(), b: t.string() };
const input = { a: "a", b: "b", c: "c" };const user = t.object(schema, t.ObjectTypeMode.STRICT);
user.parse(input); // throws an TypeParseErrorconst user = t.object(schema, t.ObjectTypeMode.STRIP);
user.parse(input); // { a: string, b: string }const user = t.object(schema, t.ObjectTypeMode.PASSTHROUGH);
user.parse(input); // { a: string, b: string, c: string }
```## object helpers
### .strict()
```ts
t.object(schema).strict();
// same as
t.object(schema, t.ObjectTypeMode.STRICT);
```### .strip()
```ts
t.object(schema).strip();
// same as
t.object(schema, t.ObjectTypeMode.STRIP);
```### .passthrough()
```ts
t.object(schema).passthrough();
// same as
t.object(schema, t.ObjectTypeMode.PASSTHROUGH);
```## union(...type)
```ts
const uni = t.union(t.string(), t.number()); // string | number
```## union(type[])
```ts
const tpl = t.union([t.string(), t.number(), t.string()]); // string | number
```💔 The following code does not work, TypeScript can not infer array values properly. Use the `as const` workaround to do this.
```ts
const types = [t.string(), t.number(), t.string()];
const tpl = t.union(types); // string | number
```## union(type[] as const)
```ts
const types = [t.string(), t.number(), t.string()] as const;
const tpl = t.union(types); // string | number
```## optional(type)
```ts
const user = t.object({
name: t.string(),
age: t.optional(t.number()),
});
// { name: string, age?: number }
```## enum(...string)
```ts
const myEnum = t.enum("UP", "DOWN", "LEFT", "RIGHT");
```### Access enum properties
```ts
myEnum.enum.UP; // === "UP"
myEnum.enum.PLOP; // error: PLOP does not exists
myEnum.enum.DOWN = "prout"; // error: it is read-only(property) enum: {
readonly UP: "UP";
readonly DOWN: "DOWN";
readonly LEFT: "LEFT";
readonly RIGHT: "RIGHT";
}
```### Access enum values
```ts
myEnum.options[1]; // === "DOWN"(property) options: ["UP", "DOWN", "LEFT", "RIGHT"]
```### Test enum values
```ts
myEnum.parse(myEnum.enum.LEFT); // => "LEFT"
myEnum.parse("LEFT"); // => "LEFT"
myEnum.parse("2"); // => "LEFT"
myEnum.parse(2); // => "LEFT"
myEnum.parse("PLOP"); // error: expected '0|1|2|3|UP|DOWN|LEFT|RIGHT' got 'string'
```### Infer enum type
```ts
type MyEnum = t.infer; // => "UP" | "DOWN" | "LEFT" | "RIGHT"function move(direction: MyEnum) {
// direction === "DOWN"
}move(myEnum.enum.DOWN);
```## enum(string[])
```ts
const myEnum = t.enum(["UP", "DOWN", "LEFT", "RIGHT"]);
```💔 The following code does not work, TypeScript can not infer array values properly. Use the `as const` workaround to do this.
```ts
const values = ["UP", "DOWN", "LEFT", "RIGHT"];
const myEnum = t.enum(values);
```## enum(string[] as const)
```ts
const myEnum = t.enum(["UP", "DOWN", "LEFT", "RIGHT"] as const);
``````ts
const values = ["UP", "DOWN", "LEFT", "RIGHT"] as const;
const myEnum = t.enum(values);
```## enum(object)
```ts
const myEnum = t.enum({ UP: "UP", DOWN: "DOWN", LEFT: 42, RIGHT: 43 });
```💔 The following code does not work, TypeScript can not infer object properties properly. Use the `as const` workaround to do this.
```ts
const values = { UP: "UP", DOWN: "DOWN", LEFT: 42, RIGHT: 43 };
const myEnum = t.enum(values);
```## enum(object as const)
```ts
const values = { UP: "UP", DOWN: "DOWN", LEFT: 42, RIGHT: 43 } as const;
const myEnum = t.enum(values);
```## enum(enum)
```ts
enum MyEnum {
UP = "UP",
DOWN = "DOWN",
LEFT = 42,
RIGHT,
}const myEnum = t.enum(MyEnum);
```## nativeEnum(enum)
Alias: `enum(enum)`
```ts
enum MyEnum {
UP = "UP",
DOWN = "DOWN",
LEFT = 42,
RIGHT,
}const myEnum = t.nativeEnum(MyEnum);
```## instanceof(type)
```ts
class MyClass {}const instance = new MyClass();
t.instanceof(MyClass).parse(instance); // passes
t.instanceof(MyClass).parse("nyan"); // fail
```## date()
```ts
t.date().parse(new Date()); // passes
t.date().parse("2022-01-12T00:00:00.000Z"); // passes
t.date().parse("not a string date"); // fail
```## record(type)
```ts
t.record(t.string()); // { [x: string]: string }
t.record(t.number()); // { [x: string]: number }
t.record(t.date()); // { [x: string]: Date }
```## set(type)
Testing a single type on the entire set
```ts
t.set(t.string()); // Set
```Testing a union of types on the entire set
```ts
t.set(t.union(t.string(), t.boolean(), t.string())); // Set
```## set(...type)
Same as [tuple(...type)](#tupletype) but test if the input is an instance of Set.
## set([type, ...type])
Testing a tuple of types on the Set
```ts
t.set(t.string(), t.boolean(), t.string()); // Set<[string, boolean, string]>
t.set([t.string(), t.boolean(), t.string()]); // Set<[string, boolean, string]>
```## map(keyType, valueType)
```ts
t.map(t.string(), t.number()); // Map
t.map(t.date(), t.string()); // Map
```## map(schema)
Same as [object(schema)](#objectschema) but test if the input is an instance of Map.
```ts
const map = new Map();t.map({ name: t.string(), size: t.string() }).parse(map);
```## promise(type)
```ts
const promise = t.promise(t.number());await promise.parse(Promise.resolve(42)); // resolve: 42
await promise.parse(Promise.resolve("42")); // reject: expected 'number' got 'string'
await promise.parse(42); // reject: expected 'Promise' got 'number'
```## function()
```ts
const func = t.function();type Func = t.infer; // () => void
```## function(args)
```ts
const func = t.function([t.string(), t.number()]);type Func = t.infer; // (arg_0: string, arg_1: number) => void
```## function(args, returns)
```ts
const func = t.function([t.string()], t.boolean());type Func = t.infer; // (arg_0: string) => boolean
```## function(args, returns, implement)
```ts
const args = [t.string(), t.boolean()] as const;const returns = t.union(t.string(), t.number());
const func = t.function(args, returns, (input, toInt) => {
// input type is string and toInt type is boolean
return toInt ? parseInt(input) : input.toUpperCase();
});type Func = t.infer; // (arg_0: string, arg_1: boolean) => string | number
```## preprocess(filter, type)
If you want to modify the input before it is parsed you can use the `preprocess` type as follows.
```ts
const toString = t.preprocess((input) => String(input), t.string());toString.parse("42"); // => "42"
toString.parse(42); // => "42"
```## postprocess(filter, type)
If you want to modify the output after it is parsed you can use the `postprocess` type as follows.
```ts
const postprocess = t.postprocess((input) => input + 2, t.number());postprocess.parse(40); // => 42
postprocess.parse("42"); // throws: "expected 'number' got 'string'"
```## postprocess(filter, inputType, outputType)
If you want to modify the output after it is parsed you can use the `postprocess` type as follows.
```ts
const postprocess = t.postprocess(
(input) => String(input),
t.number(),
t.string(),
);postprocess.parse(40); // => "42"
postprocess.parse("42"); // => throws: "expected 'number' got 'string'"
```# Type helpers
## safeParse(input)
If you want to avoid the parse method throws an error you can use the `.safeParse()` method instead.
```ts
t.bigint().safeParse(42n);
// => { success: true, data: 42n }t.bigint().safeParse(42);
// => {
// "error": [TypeParseError: expected 'bigint|undefined' got 'number'],
// "success": false,
// }
```## optional()
```ts
t.bigint().optional(); // => bigint | undefined// same as
t.optional(t.bigint());
```## preprocess()
```ts
t.string().preprocess((input) => String(input));// same as
t.preprocess((input) => String(input), t.string());
```## postprocess()
Alias: `.transform()`
```ts
t.number().postprocess((input) => input + 2);// same as
t.postprocess((input) => input + 2, t.number());
```# Contributing 💜
See [CONTRIBUTING.md](https://github.com/skarab42/tson/blob/main/CONTRIBUTING.md)