Install
npm
$ npm install @sinclair/typebox --save
deno
import { Static, Type } from 'npm:@sinclair/typebox'
esm
import { Static, Type } from 'https://esm.sh/@sinclair/typebox'
Usage
import { Static, Type } from '@sinclair/typebox'
const T = Type.Object({ // const T = {
x: Type.Number(), // type: 'object',
y: Type.Number(), // required: ['x', 'y', 'z'],
z: Type.Number() // properties: {
}) // x: { type: 'number' },
// y: { type: 'number' },
// z: { type: 'number' }
// }
// }
type T = Static<typeof T> // type T = {
// x: number,
// y: number,
// z: number
// }
Overview
TypeBox is a type builder library that creates in-memory JSON Schema objects that can be statically inferred as TypeScript types. The schemas produced by this library are designed to match the static type checking rules of the TypeScript compiler. TypeBox enables one to create a unified type that can be statically checked by TypeScript and runtime asserted using standard JSON Schema validation.
TypeBox is designed to enable JSON schema to compose with the same flexibility as TypeScript's type system. It can be used either as a simple tool to build up complex schemas or integrated into REST and RPC services to help validate data received over the wire.
License MIT
Contents
Example
The following demonstrates TypeBox's general usage.
import { Static, Type } from '@sinclair/typebox'
//--------------------------------------------------------------------------------------------
//
// Let's say you have the following type ...
//
//--------------------------------------------------------------------------------------------
type T = {
id: string,
name: string,
timestamp: number
}
//--------------------------------------------------------------------------------------------
//
// ... you can express this type in the following way.
//
//--------------------------------------------------------------------------------------------
const T = Type.Object({ // const T = {
id: Type.String(), // type: 'object',
name: Type.String(), // properties: {
timestamp: Type.Integer() // id: {
}) // type: 'string'
// },
// name: {
// type: 'string'
// },
// timestamp: {
// type: 'integer'
// }
// },
// required: [
// 'id',
// 'name',
// 'timestamp'
// ]
// }
//--------------------------------------------------------------------------------------------
//
// ... then infer back to the original static type this way.
//
//--------------------------------------------------------------------------------------------
type T = Static<typeof T> // type T = {
// id: string,
// name: string,
// timestamp: number
// }
//--------------------------------------------------------------------------------------------
//
// ... then use the type both as JSON schema and as a TypeScript type.
//
//--------------------------------------------------------------------------------------------
function receive(value: T) { // ... as a Type
if(JSON.validate(T, value)) { // ... as a Schema
// ok...
}
}
Types
TypeBox provides a set of functions that allow you to compose JSON Schema similar to how you would compose static types with TypeScript. Each function creates a JSON schema fragment which can compose into more complex types. The schemas produced by TypeBox can be passed directly to any JSON Schema compliant validator, or used to reflect runtime metadata for a type.
Standard
The following table lists the Standard TypeBox types.
┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐
│ TypeBox │ TypeScript │ JSON Schema │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Any() │ type T = any │ const T = { } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Unknown() │ type T = unknown │ const T = { } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.String() │ type T = string │ const T = { │
│ │ │ type: 'string' │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Number() │ type T = number │ const T = { │
│ │ │ type: 'number' │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Integer() │ type T = number │ const T = { │
│ │ │ type: 'integer' │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Boolean() │ type T = boolean │ const T = { │
│ │ │ type: 'boolean' │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Null() │ type T = null │ const T = { │
│ │ │ type: 'null' │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.RegEx(/foo/) │ type T = string │ const T = { │
│ │ │ type: 'string', │
│ │ │ pattern: 'foo' │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Literal(42) │ type T = 42 │ const T = { │
│ │ │ const: 42, │
│ │ │ type: 'number' │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Array( │ type T = number[] │ const T = { │
│ Type.Number() │ │ type: 'array', │
│ ) │ │ items: { │
│ │ │ type: 'number' │
│ │ │ } │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Object({ │ type T = { │ const T = { │
│ x: Type.Number(), │ x: number, │ type: 'object', │
│ y: Type.Number() │ y: number │ properties: { │
│ }) │ } │ x: { │
│ │ │ type: 'number' │
│ │ │ }, │
│ │ │ y: { │
│ │ │ type: 'number' │
│ │ │ } │
│ │ │ }, │
│ │ │ required: ['x', 'y'] │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Tuple([ │ type T = [number, number] │ const T = { │
│ Type.Number(), │ │ type: 'array', │
│ Type.Number() │ │ items: [{ │
│ ]) │ │ type: 'number' │
│ │ │ }, { │
│ │ │ type: 'number' │
│ │ │ }], │
│ │ │ additionalItems: false, │
│ │ │ minItems: 2, │
│ │ │ maxItems: 2 │
│ │ │ } │
│ │ │ │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ enum Foo { │ enum Foo { │ const T = { │
│ A, │ A, │ anyOf: [{ │
│ B │ B │ type: 'number', │
│ } │ } │ const: 0 │
│ │ │ }, { │
│ const T = Type.Enum(Foo) │ type T = Foo │ type: 'number', │
│ │ │ const: 1 │
│ │ │ }] │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.KeyOf( │ type T = keyof { │ const T = { │
│ Type.Object({ │ x: number, │ anyOf: [{ │
│ x: Type.Number(), │ y: number │ type: 'string', │
│ y: Type.Number() │ } │ const: 'x' │
│ }) │ │ }, { │
│ ) │ │ type: 'string', │
│ │ │ const: 'y' │
│ │ │ }] │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Union([ │ type T = string | number │ const T = { │
│ Type.String(), │ │ anyOf: [{ │
│ Type.Number() │ │ type: 'string' │
│ ]) │ │ }, { │
│ │ │ type: 'number' │
│ │ │ }] │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Intersect([ │ type T = { │ const T = { │
│ Type.Object({ │ x: number │ type: 'object', │
│ x: Type.Number() │ } & { │ properties: { │
│ }), │ y: number │ x: { │
│ Type.Object({ │ } │ type: 'number' │
│ y: Type.Number() │ │ }, │
│ }) │ │ y: { │
│ ]) │ │ type: 'number' │
│ │ │ } │
│ │ │ }, │
│ │ │ required: ['x', 'y'] │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Never() │ type T = never │ const T = { │
│ │ │ allOf: [{ │
│ │ │ type: 'boolean', │
│ │ │ const: false │
│ │ │ }, { │
│ │ │ type: 'boolean', │
│ │ │ const: true │
│ │ │ }] │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Record( │ type T = Record< │ const T = { │
│ Type.String(), │ string, │ type: 'object', │
│ Type.Number() │ number, │ patternProperties: { │
│ ) │ > │ '^.*$': { │
│ │ │ type: 'number' │
│ │ │ } │
│ │ │ } │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Partial( │ type T = Partial<{ │ const T = { │
│ Type.Object({ │ x: number, │ type: 'object', │
│ x: Type.Number(), │ y: number │ properties: { │
│ y: Type.Number() | }> │ x: { │
│ }) │ │ type: 'number' │
│ ) │ │ }, │
│ │ │ y: { │
│ │ │ type: 'number' │
│ │ │ } │
│ │ │ } │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Required( │ type T = Required<{ │ const T = { │
│ Type.Object({ │ x?: number, │ type: 'object', │
│ x: Type.Optional( │ y?: number │ properties: { │
│ Type.Number() | }> │ x: { │
│ ), │ │ type: 'number' │
│ y: Type.Optional( │ │ }, │
│ Type.Number() │ │ y: { │
│ ) │ │ type: 'number' │
│ }) │ │ } │
│ ) │ │ }, │
│ │ │ required: ['x', 'y'] │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Pick( │ type T = Pick<{ │ const T = { │
│ Type.Object({ │ x: number, │ type: 'object', │
│ x: Type.Number(), │ y: number │ properties: { │
│ y: Type.Number() | }, 'x'> │ x: { │
│ }), ['x'] │ │ type: 'number' │
│ ) │ │ } │
│ │ │ }, │
│ │ │ required: ['x'] │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Omit( │ type T = Omit<{ │ const T = { │
│ Type.Object({ │ x: number, │ type: 'object', │
│ x: Type.Number(), │ y: number │ properties: { │
│ y: Type.Number() | }, 'x'> │ y: { │
│ }), ['x'] │ │ type: 'number' │
│ ) │ │ } │
│ │ │ }, │
│ │ │ required: ['y'] │
│ │ │ } │
│ │ │ │
└────────────────────────────────┴─────────────────────────────┴────────────────────────────────┘
Extended
TypeBox provides a set of extended types that can be used to express schematics for core JavaScript constructs and primitives. Extended types are not valid JSON Schema and will not validate using typical validation. These types however can be used to frame JSON schema and describe callable RPC interfaces that may receive JSON validated data.
┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐
│ TypeBox │ TypeScript │ Extended Schema │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Constructor([ │ type T = new ( │ const T = { │
│ Type.String(), │ arg0: string, │ type: 'object', │
│ Type.Number() │ arg1: number │ instanceOf: 'Constructor', │
│ ], Type.Boolean()) │ ) => boolean │ parameters: [{ │
│ │ │ type: 'string' │
│ │ │ }, { │
│ │ │ type: 'number' │
│ │ │ }], │
│ │ │ return: { │
│ │ │ type: 'boolean' │
│ │ │ } │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Function([ │ type T = ( │ const T = { │
| Type.String(), │ arg0: string, │ type : 'object', │
│ Type.Number() │ arg1: number │ instanceOf: 'Function', │
│ ], Type.Boolean()) │ ) => boolean │ parameters: [{ │
│ │ │ type: 'string' │
│ │ │ }, { │
│ │ │ type: 'number' │
│ │ │ }], │
│ │ │ return: { │
│ │ │ type: 'boolean' │
│ │ │ } │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Promise( │ type T = Promise<string> │ const T = { │
│ Type.String() │ │ type: 'object', │
│ ) │ │ instanceOf: 'Promise', │
│ │ │ item: { │
│ │ │ type: 'string' │
│ │ │ } │
│ │ │ } │
│ │ │ │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Uint8Array() │ type T = Uint8Array │ const T = { │
│ │ │ type: 'object', │
│ │ │ instanceOf: 'Uint8Array' │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Date() │ type T = Date │ const T = { │
│ │ │ type: 'object', │
│ │ │ instanceOf: 'Date' │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Undefined() │ type T = undefined │ const T = { │
│ │ │ type: 'null', │
│ │ │ typeOf: 'Undefined' │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Void() │ type T = void │ const T = { │
│ │ │ type: 'null' │
│ │ │ typeOf: 'Void' │
│ │ │ } │
│ │ │ │
└────────────────────────────────┴─────────────────────────────┴────────────────────────────────┘
Modifiers
TypeBox provides modifiers that can be applied to an objects properties. This allows for optional
and readonly
to be applied to that property. The following table illustates how they map between TypeScript and JSON Schema.
┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐
│ TypeBox │ TypeScript │ JSON Schema │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Object({ │ type T = { │ const T = { │
│ name: Type.Optional( │ name?: string │ type: 'object', │
│ Type.String() │ } │ properties: { │
│ ) │ │ name: { │
│ }) │ │ type: 'string' │
│ │ │ } │
│ │ │ } │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Object({ │ type T = { │ const T = { │
│ name: Type.Readonly( │ readonly name: string │ type: 'object', │
│ Type.String() │ } │ properties: { │
│ ) │ │ name: { │
│ }) │ │ type: 'string' │
│ │ │ } │
│ │ │ }, │
│ │ │ required: ['name'] │
│ │ │ } │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Type.Object({ │ type T = { │ const T = { │
│ name: Type.ReadonlyOptional( │ readonly name?: string │ type: 'object', │
│ Type.String() │ } │ properties: { │
│ ) │ │ name: { │
│ }) │ │ type: 'string' │
│ │ │ } │
│ │ │ } │
│ │ │ } │
│ │ │ │
└────────────────────────────────┴─────────────────────────────┴────────────────────────────────┘
Options
You can pass additional JSON schema options on the last argument of any given type. The following are some examples.
// string must be an email
const T = Type.String({ format: 'email' })
// number must be a multiple of 2
const T = Type.Number({ multipleOf: 2 })
// array must have at least 5 integer values
const T = Type.Array(Type.Integer(), { minItems: 5 })
Reference
Use Type.Ref(...)
to create referenced types. The target type must specify an $id
.
const T = Type.String({ $id: 'T' }) // const T = {
// $id: 'T',
// type: 'string'
// }
const R = Type.Ref(T) // const R = {
// $ref: 'T'
// }
Recursive
Use Type.Recursive(...)
to create recursive types.
const Node = Type.Recursive(Node => Type.Object({ // const Node = {
id: Type.String(), // $id: 'Node',
nodes: Type.Array(Node) // type: 'object',
}), { $id: 'Node' }) // properties: {
// id: {
// type: 'string'
// },
// nodes: {
// type: 'array',
// items: {
// $ref: 'Node'
// }
// }
// },
// required: [
// 'id',
// 'nodes'
// ]
// }
type Node = Static<typeof Node> // type Node = {
// id: string
// nodes: Node[]
// }
function test(node: Node) {
const id = node.nodes[0].nodes[0] // id is string
.nodes[0].nodes[0]
.id
}
Generic
Use functions to create generic types. The following creates a generic Nullable<T>
type.
import { Type, Static, TSchema } from '@sinclair/typebox'
const Nullable = <T extends TSchema>(type: T) => Type.Union([type, Type.Null()])
const T = Nullable(Type.String()) // const T = {
// anyOf: [{
// type: 'string'
// }, {
// type: 'null'
// }]
// }
type T = Static<typeof T> // type T = string | null
const U = Nullable(Type.Number()) // const U = {
// anyOf: [{
// type: 'number'
// }, {
// type: 'null'
// }]
// }
type U = Static<typeof U> // type U = number | null
Conditional
Use the conditional module to create Conditional Types. This module implements TypeScript's structural equivalence checks to enable TypeBox types to be conditionally inferred at runtime. This module also provides the Extract and Exclude utility types which are expressed as conditional types in TypeScript.
The conditional module is provided as an optional import.
import { Conditional } from '@sinclair/typebox/conditional'
The following table shows the TypeBox mappings between TypeScript and JSON schema.
┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐
│ TypeBox │ TypeScript │ JSON Schema │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Conditional.Extends( │ type T = │ const T = { │
│ Type.String(), │ string extends number │ const: false, │
│ Type.Number(), │ true : false │ type: 'boolean' │
│ Type.Literal(true), │ │ } │
│ Type.Literal(false) │ │ │
│ ) │ │ │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Conditional.Extract( │ type T = Extract< │ const T = { │
│ Type.Union([ │ 'a' | 'b' | 'c', │ anyOf: [{ │
│ Type.Literal('a'), │ 'a' | 'f' │ const: 'a' │
│ Type.Literal('b'), │ > │ type: 'string' │
│ Type.Literal('c') │ │ }] │
│ ]), │ │ } │
│ Type.Union([ │ │ │
│ Type.Literal('a'), │ │ │
│ Type.Literal('f') │ │ │
│ ]) │ │ │
│ ) │ │ │
│ │ │ │
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ const T = Conditional.Exclude( │ type T = Exclude< │ const T = { │
│ Type.Union([ │ 'a' | 'b' | 'c', │ anyOf: [{ │
│ Type.Literal('a'), │ 'a' │ const: 'b', │
│ Type.Literal('b'), │ > │ type: 'string' │
│ Type.Literal('c') │ │ }, { │
│ ]), │ │ const: 'c', │
│ Type.Union([ │ │ type: 'string' │
│ Type.Literal('a') │ │ }] │
│ ]) │ │ } │
│ ) │ │ │
│ │ │ │
└────────────────────────────────┴─────────────────────────────┴────────────────────────────────┘
Unsafe
Use Type.Unsafe(...)
to create custom schemas with user defined inference rules.
const T = Type.Unsafe<string>({ type: 'number' }) // const T = {
// type: 'number'
// }
type T = Static<typeof T> // type T = string
This function can be used to create custom schemas for validators that require specific schema representations. An example of this might be OpenAPI's nullable
and enum
schemas which are not provided by TypeBox. The following demonstrates using Type.Unsafe(...)
to create these types.
import { Type, Static, TSchema } from '@sinclair/typebox'
//--------------------------------------------------------------------------------------------
//
// Nullable<T>
//
//--------------------------------------------------------------------------------------------
function Nullable<T extends TSchema>(schema: T) {
return Type.Unsafe<Static<T> | null>({ ...schema, nullable: true })
}
const T = Nullable(Type.String()) // const T = {
// type: 'string',
// nullable: true
// }
type T = Static<typeof T> // type T = string | null
//--------------------------------------------------------------------------------------------
//
// StringEnum<string[]>
//
//--------------------------------------------------------------------------------------------
function StringEnum<T extends string[]>(values: [...T]) {
return Type.Unsafe<T[number]>({ type: 'string', enum: values })
}
const T = StringEnum(['A', 'B', 'C']) // const T = {
// enum: ['A', 'B', 'C']
// }
type T = Static<typeof T> // type T = 'A' | 'B' | 'C'
Guards
Use the guard module to test if values are TypeBox types.
import { TypeGuard } from '@sinclair/typebox/guard'
const T = Type.String()
if(TypeGuard.TString(T)) {
// T is TString
}
Strict
TypeBox schemas contain the Kind
and Modifier
symbol properties. These properties are provided to enable runtime type reflection on schemas, as well as helping TypeBox internally compose types. These properties are not strictly valid JSON schema; so in some cases it may be desirable to omit them. TypeBox provides a Type.Strict()
function that will omit these properties if necessary.
const T = Type.Object({ // const T = {
name: Type.Optional(Type.String()) // [Kind]: 'Object',
}) // type: 'object',
// properties: {
// name: {
// [Kind]: 'String',
// type: 'string',
// [Modifier]: 'Optional'
// }
// }
// }
const U = Type.Strict(T) // const U = {
// type: 'object',
// properties: {
// name: {
// type: 'string'
// }
// }
// }
Values
TypeBox includes an optional values module that can be used to perform common operations on JavaScript values. This module enables one to create, check and cast values from types. It also provides functionality to check equality, clone and diff and patch JavaScript values. The value module is provided as an optional import.
import { Value } from '@sinclair/typebox/value'
Create
Use the Create function to create a value from a TypeBox type. TypeBox will use default values if specified.
const T = Type.Object({ x: Type.Number(), y: Type.Number({ default: 42 }) })
const A = Value.Create(T) // const A = { x: 0, y: 42 }
Clone
Use the Clone function to deeply clone a value
const A = Value.Clone({ x: 1, y: 2, z: 3 }) // const A = { x: 1, y: 2, z: 3 }
Check
Use the Check function to type check a value
const T = Type.Object({ x: Type.Number() })
const R = Value.Check(T, { x: 1 }) // const R = true
Cast
Use the Cast function to cast a value into a type. The cast function will retain as much information as possible from the original value.
const T = Type.Object({ x: Type.Number(), y: Type.Number() }, { additionalProperties: false })
const X = Value.Cast(T, null) // const X = { x: 0, y: 0 }
const Y = Value.Cast(T, { x: 1 }) // const Y = { x: 1, y: 0 }
const Z = Value.Cast(T, { x: 1, y: 2, z: 3 }) // const Z = { x: 1, y: 2 }
Equal
Use the Equal function to deeply check for value equality.
const R = Value.Equal( // const R = true
{ x: 1, y: 2, z: 3 },
{ x: 1, y: 2, z: 3 }
)
Hash
Use the Hash function to create a FNV1A-64 non cryptographic hash of a value.
const A = Value.Hash({ x: 1, y: 2, z: 3 }) // const A = 2910466848807138541n
const B = Value.Hash({ x: 1, y: 4, z: 3 }) // const B = 1418369778807423581n
Diff
Use the Diff function to produce a sequence of edits to transform one value into another.
const E = Value.Diff( // const E = [
{ x: 1, y: 2, z: 3 }, // { type: 'update', path: '/y', value: 4 },
{ y: 4, z: 5, w: 6 } // { type: 'update', path: '/z', value: 5 },
) // { type: 'insert', path: '/w', value: 6 },
// { type: 'delete', path: '/x' }
// ]
Patch
Use the Patch function to apply edits
const A = { x: 1, y: 2 }
const B = { x: 3 }
const E = Value.Diff(A, B) // const E = [
// { type: 'update', path: '/x', value: 3 },
// { type: 'delete', path: '/y' }
// ]
const C = Value.Patch<typeof B>(A, E) // const C = { x: 3 }
Errors
Use the Errors function enumerate validation errors.
const T = Type.Object({ x: Type.Number(), y: Type.Number() })
const R = [...Value.Errors(T, { x: '42' })] // const R = [{
// schema: { type: 'number' },
// path: '/x',
// value: '42',
// message: 'Expected number'
// }, {
// schema: { type: 'number' },
// path: '/y',
// value: undefined,
// message: 'Expected number'
// }]
Pointer
Use ValuePointer to perform mutable updates on existing values using RFC6901 Json Pointers.
import { ValuePointer } from '@sinclair/typebox/value'
const A = { x: 0, y: 0, z: 0 }
ValuePointer.Set(A, '/x', 1) // const A = { x: 1, y: 0, z: 0 }
ValuePointer.Set(A, '/y', 1) // const A = { x: 1, y: 1, z: 0 }
ValuePointer.Set(A, '/z', 1) // const A = { x: 1, y: 1, z: 1 }
TypeCheck
TypeBox targets JSON Schema Draft 6 and is built and tested against the Ajv JSON Schema validator for standards compliance. TypeBox also includes an optional built-in TypeCompiler that can provide improved compilation and validation performance specifically for TypeBox types only.
The following sections detail using these validators.
TypeCompiler
TypeBox includes an high performance just-in-time (JIT) compiler and type checker that can be used in applications that require extremely fast validation. Note that this compiler is optimized for TypeBox types only where the schematics are known in advance.
The compiler module is provided as an optional import.
import { TypeCompiler } from '@sinclair/typebox/compiler'
Use the Compile(...)
function to compile a type.
const C = TypeCompiler.Compile(Type.Object({ // const C: TypeCheck<TObject<{
x: Type.Number(), // x: TNumber;
y: Type.Number(), // y: TNumber;
z: Type.Number() // z: TNumber;
})) // }>>
const R = C.Check({ x: 1, y: 2, z: 3 }) // const R = true
Validation errors can be read with the Errors(...)
function.
const C = TypeCompiler.Compile(Type.Object({ // const C: TypeCheck<TObject<{
x: Type.Number(), // x: TNumber;
y: Type.Number(), // y: TNumber;
z: Type.Number() // z: TNumber;
})) // }>>
const value = { }
const errors = [...C.Errors(value)] // const errors = [{
// schema: { type: 'number' },
// path: '/x',
// value: undefined,
// message: 'Expected number'
// }, {
// schema: { type: 'number' },
// path: '/y',
// value: undefined,
// message: 'Expected number'
// }, {
// schema: { type: 'number' },
// path: '/z',
// value: undefined,
// message: 'Expected number'
// }]
Compiled routines can be inspected with the .Code()
function.
const C = TypeCompiler.Compile(Type.String()) // const C: TypeCheck<TString>
console.log(C.Code()) // return function check(value) {
// return (
// (typeof value === 'string')
// )
// }
Ajv
The following are the recommended configurations to support both the Standard and Extended type sets provided by TypeBox. For schema portability and publishing to remote systems, it is recommended to use the Standard type set only.
$ npm install ajv ajv-formats --save
Standard Ajv Configuration
Expand for Standard Type Set Configuration
import { Type } from '@sinclair/typebox'
import addFormats from 'ajv-formats'
import Ajv from 'ajv'
export function createAjv() {
return addFormats(new Ajv({}), [
'date-time',
'time',
'date',
'email',
'hostname',
'ipv4',
'ipv6',
'uri',
'uri-reference',
'uuid',
'uri-template',
'json-pointer',
'relative-json-pointer',
'regex'
])
}
const ajv = createAjv()
const R = ajv.validate(Type.Object({ // const R = true
x: Type.Number(),
y: Type.Number(),
z: Type.Number()
}), { x: 1, y: 2, z: 3 })
Extended Ajv Configuration
Expand for Extended Type Set Configuration
import { TypeGuard } from '@sinclair/typebox/guard'
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import addFormats from 'ajv-formats'
import Ajv from 'ajv'
function schemaOf(schemaOf: string, value: unknown, schema: unknown) {
switch (schemaOf) {
case 'Constructor':
return TypeGuard.TConstructor(schema) && Value.Check(schema, value) // not supported
case 'Function':
return TypeGuard.TFunction(schema) && Value.Check(schema, value) // not supported
case 'Date':
return TypeGuard.TDate(schema) && Value.Check(schema, value)
case 'Promise':
return TypeGuard.TPromise(schema) && Value.Check(schema, value) // not supported
case 'Uint8Array':
return TypeGuard.TUint8Array(schema) && Value.Check(schema, value)
case 'Undefined':
return TypeGuard.TUndefined(schema) && Value.Check(schema, value) // not supported
case 'Void':
return TypeGuard.TVoid(schema) && Value.Check(schema, value)
default:
return false
}
}
export function createAjv() {
return addFormats(new Ajv({}), [
'date-time',
'time',
'date',
'email',
'hostname',
'ipv4',
'ipv6',
'uri',
'uri-reference',
'uuid',
'uri-template',
'json-pointer',
'relative-json-pointer',
'regex'
])
.addKeyword({ type: 'object', keyword: 'instanceOf', validate: schemaOf })
.addKeyword({ type: 'null', keyword: 'typeOf', validate: schemaOf })
.addKeyword('exclusiveMinimumTimestamp')
.addKeyword('exclusiveMaximumTimestamp')
.addKeyword('minimumTimestamp')
.addKeyword('maximumTimestamp')
.addKeyword('minByteLength')
.addKeyword('maxByteLength')
}
const ajv = createAjv()
const R = ajv.validate(Type.Object({ // const R = true
buffer: Type.Uint8Array(),
date: Type.Date(),
void: Type.Void()
}), {
buffer: new Uint8Array(),
date: new Date(),
void: null
})
TypeSystem
TypeBox provides an extensible TypeSystem module that enables developers to register additional types above and beyond the standard or extended type set. This module also allows developers to define custom string formats as well as override certain type checking behaviours.
The TypeSystem module is provided as an optional import.
import { TypeSystem } from '@sinclair/typebox/system'
Types
Use the CreateType(...)
function to specify and return a custom type. This function will return a type factory function that can be used to construct the type. The following creates and registers a BigNumber type which will statically infer as bigint
.
//--------------------------------------------------------------------------------------------
//
// Use TypeSystem.CreateType(...) to define and return a type factory function
//
//--------------------------------------------------------------------------------------------
type BigNumberOptions = { minimum?: bigint; maximum?: bigint }
const BigNumber = TypeSystem.CreateType<bigint, BigNumberOptions>(
'BigNumber',
(options, value) => {
if (typeof value !== 'bigint') return false
if (options.maximum !== undefined && value > options.maximum) return false
if (options.minimum !== undefined && value < options.minimum) return false
return true
}
)
//--------------------------------------------------------------------------------------------
//
// Use the custom type like any other type
//
//--------------------------------------------------------------------------------------------
const T = BigNumber({ minimum: 10n, maximum: 20n }) // const T = {
// minimum: 10n,
// maximum: 20n,
// [Symbol(TypeBox.Kind)]: 'BigNumber'
// }
const C = TypeCompiler.Compile(T)
const X = C.Check(15n) // const X = true
const Y = C.Check(5n) // const Y = false
const Z = C.Check(25n) // const Z = false
Formats
Use the CreateFormat(...)
function to specify user defined string formats. The following creates a custom string format that checks for lowercase.
//--------------------------------------------------------------------------------------------
//
// Use TypeSystem.CreateFormat(...) to define a custom string format
//
//--------------------------------------------------------------------------------------------
TypeSystem.CreateFormat('lowercase', value => value === value.toLowerCase())
//--------------------------------------------------------------------------------------------
//
// Use the format by creating string types with the 'format' option
//
//--------------------------------------------------------------------------------------------
const T = Type.String({ format: 'lowercase' })
const A = Value.Check(T, 'action') // const A = true
const B = Value.Check(T, 'ACTION') // const B = false
Benchmark
This project maintains a set of benchmarks that measure Ajv, Value and TypeCompiler compilation and validation performance. These benchmarks can be run locally by cloning this repository and running npm run benchmark
. The results below show for Ajv version 8.11.2.
For additional comparative benchmarks, please refer to typescript-runtime-type-benchmarks.
Compile
This benchmark measures compilation performance for varying types. You can review this benchmark here.
┌──────────────────┬────────────┬──────────────┬──────────────┬──────────────┐
│ (index) │ Iterations │ Ajv │ TypeCompiler │ Performance │
├──────────────────┼────────────┼──────────────┼──────────────┼──────────────┤
│ Number │ 2000 │ ' 418 ms' │ ' 14 ms' │ ' 29.86 x' │
│ String │ 2000 │ ' 331 ms' │ ' 12 ms' │ ' 27.58 x' │
│ Boolean │ 2000 │ ' 290 ms' │ ' 13 ms' │ ' 22.31 x' │
│ Null │ 2000 │ ' 253 ms' │ ' 8 ms' │ ' 31.63 x' │
│ RegEx │ 2000 │ ' 481 ms' │ ' 18 ms' │ ' 26.72 x' │
│ ObjectA │ 2000 │ ' 2675 ms' │ ' 54 ms' │ ' 49.54 x' │
│ ObjectB │ 2000 │ ' 2849 ms' │ ' 39 ms' │ ' 73.05 x' │
│ Tuple │ 2000 │ ' 1224 ms' │ ' 22 ms' │ ' 55.64 x' │
│ Union │ 2000 │ ' 1225 ms' │ ' 26 ms' │ ' 47.12 x' │
│ Vector4 │ 2000 │ ' 1777 ms' │ ' 24 ms' │ ' 74.04 x' │
│ Matrix4 │ 2000 │ ' 825 ms' │ ' 12 ms' │ ' 68.75 x' │
│ Literal_String │ 2000 │ ' 345 ms' │ ' 9 ms' │ ' 38.33 x' │
│ Literal_Number │ 2000 │ ' 363 ms' │ ' 7 ms' │ ' 51.86 x' │
│ Literal_Boolean │ 2000 │ ' 358 ms' │ ' 6 ms' │ ' 59.67 x' │
│ Array_Number │ 2000 │ ' 687 ms' │ ' 8 ms' │ ' 85.88 x' │
│ Array_String │ 2000 │ ' 726 ms' │ ' 8 ms' │ ' 90.75 x' │
│ Array_Boolean │ 2000 │ ' 703 ms' │ ' 8 ms' │ ' 87.88 x' │
│ Array_ObjectA │ 2000 │ ' 3686 ms' │ ' 40 ms' │ ' 92.15 x' │
│ Array_ObjectB │ 2000 │ ' 3821 ms' │ ' 40 ms' │ ' 95.53 x' │
│ Array_Tuple │ 2000 │ ' 2070 ms' │ ' 17 ms' │ ' 121.76 x' │
│ Array_Union │ 2000 │ ' 1503 ms' │ ' 21 ms' │ ' 71.57 x' │
│ Array_Vector4 │ 2000 │ ' 2185 ms' │ ' 21 ms' │ ' 104.05 x' │
│ Array_Matrix4 │ 2000 │ ' 1502 ms' │ ' 16 ms' │ ' 93.88 x' │
└──────────────────┴────────────┴──────────────┴──────────────┴──────────────┘
Validate
This benchmark measures validation performance for varying types. You can review this benchmark here.
┌──────────────────┬────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
│ (index) │ Iterations │ ValueCheck │ Ajv │ TypeCompiler │ Performance │
├──────────────────┼────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ Number │ 1000000 │ ' 28 ms' │ ' 6 ms' │ ' 6 ms' │ ' 1.00 x' │
│ String │ 1000000 │ ' 25 ms' │ ' 23 ms' │ ' 11 ms' │ ' 2.09 x' │
│ Boolean │ 1000000 │ ' 24 ms' │ ' 21 ms' │ ' 11 ms' │ ' 1.91 x' │
│ Null │ 1000000 │ ' 25 ms' │ ' 22 ms' │ ' 10 ms' │ ' 2.20 x' │
│ RegEx │ 1000000 │ ' 164 ms' │ ' 53 ms' │ ' 37 ms' │ ' 1.43 x' │
│ ObjectA │ 1000000 │ ' 593 ms' │ ' 47 ms' │ ' 25 ms' │ ' 1.88 x' │
│ ObjectB │ 1000000 │ ' 1053 ms' │ ' 54 ms' │ ' 40 ms' │ ' 1.35 x' │
│ Tuple │ 1000000 │ ' 129 ms' │ ' 25 ms' │ ' 16 ms' │ ' 1.56 x' │
│ Union │ 1000000 │ ' 334 ms' │ ' 25 ms' │ ' 16 ms' │ ' 1.56 x' │
│ Recursive │ 1000000 │ ' 3127 ms' │ ' 424 ms' │ ' 98 ms' │ ' 4.33 x' │
│ Vector4 │ 1000000 │ ' 152 ms' │ ' 24 ms' │ ' 12 ms' │ ' 2.00 x' │
│ Matrix4 │ 1000000 │ ' 593 ms' │ ' 41 ms' │ ' 27 ms' │ ' 1.52 x' │
│ Literal_String │ 1000000 │ ' 48 ms' │ ' 20 ms' │ ' 11 ms' │ ' 1.82 x' │
│ Literal_Number │ 1000000 │ ' 47 ms' │ ' 22 ms' │ ' 10 ms' │ ' 2.20 x' │
│ Literal_Boolean │ 1000000 │ ' 48 ms' │ ' 21 ms' │ ' 11 ms' │ ' 1.91 x' │
│ Array_Number │ 1000000 │ ' 495 ms' │ ' 32 ms' │ ' 21 ms' │ ' 1.52 x' │
│ Array_String │ 1000000 │ ' 481 ms' │ ' 31 ms' │ ' 21 ms' │ ' 1.48 x' │
│ Array_Boolean │ 1000000 │ ' 446 ms' │ ' 32 ms' │ ' 27 ms' │ ' 1.19 x' │
│ Array_ObjectA │ 1000000 │ ' 14314 ms' │ ' 2341 ms' │ ' 1969 ms' │ ' 1.19 x' │
│ Array_ObjectB │ 1000000 │ ' 16883 ms' │ ' 2661 ms' │ ' 2606 ms' │ ' 1.02 x' │
│ Array_Tuple │ 1000000 │ ' 1834 ms' │ ' 98 ms' │ ' 77 ms' │ ' 1.27 x' │
│ Array_Union │ 1000000 │ ' 4960 ms' │ ' 240 ms' │ ' 87 ms' │ ' 2.76 x' │
│ Array_Recursive │ 1000000 │ ' 56273 ms' │ ' 7118 ms' │ ' 1122 ms' │ ' 6.34 x' │
│ Array_Vector4 │ 1000000 │ ' 2498 ms' │ ' 99 ms' │ ' 48 ms' │ ' 2.06 x' │
│ Array_Matrix4 │ 1000000 │ ' 12487 ms' │ ' 383 ms' │ ' 246 ms' │ ' 1.56 x' │
└──────────────────┴────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
Compression
The following table lists esbuild compiled and minified sizes for each TypeBox module.
┌──────────────────────┬────────────┬────────────┬─────────────┐
│ (index) │ Compiled │ Minified │ Compression │
├──────────────────────┼────────────┼────────────┼─────────────┤
│ typebox/compiler │ ' 64 kb' │ ' 31 kb' │ '2.02 x' │
│ typebox/conditional │ ' 45 kb' │ ' 18 kb' │ '2.44 x' │
│ typebox/custom │ ' 0 kb' │ ' 0 kb' │ '2.61 x' │
│ typebox/format │ ' 0 kb' │ ' 0 kb' │ '2.66 x' │
│ typebox/guard │ ' 23 kb' │ ' 11 kb' │ '2.07 x' │
│ typebox/hash │ ' 4 kb' │ ' 1 kb' │ '2.30 x' │
│ typebox/value │ ' 89 kb' │ ' 41 kb' │ '2.15 x' │
│ typebox │ ' 12 kb' │ ' 6 kb' │ '1.89 x' │
└──────────────────────┴────────────┴────────────┴─────────────┘
Contribute
TypeBox is open to community contribution. Please ensure you submit an open issue before submitting your pull request. The TypeBox project preferences open community discussion prior to accepting new features.