Schemas
x.array
Parses any iterable to an array.
const schema = x.array(x.string)
assert.deepEqual(schema.parse(['a']).value, ['a'])
assert.deepEqual(schema.parse(new Set(['a'])).value, ['a'])
assert(schema.parse([1]).success === false)
assert(schema.parse(1).success === false)
assert(schema.parse({}).success === false)Access the array content schema with .item
const schema = x.array(x.string)
assert(schema.item === x.string)x.array.defaultMaxSize
Allows to configure the default max length of arrays.
The default value is intentionally low because safety-first.
If you need to increase it, I recommend increasing it locally at schema level: x.array(x.string).size({ max: 10_000 })
If you need to loosen it globally, use x.array.defaultMaxSize = 10_000
x.array.defaultMaxSize = 20
const schema = x.array(x.string)
assert(schema.parse(new Array(20).fill('x')).success === true)
assert(schema.parse(new Array(21).fill('x')).success === false)x.array.size
const schema = x.array(x.number).size({ min: 1, max: 3 })
assert(schema.parse([1]).success === true)
assert(schema.parse([]).success === false)
assert(schema.parse([1, 2, 3, 4]).success === false)x.bigint
It accepts any input that can construct a BigInt. You can use it to decode JSON coming from a JSON.parse
assert.deepEqual(x.bigint.parse(1), { success: true, value: 1n })
assert.deepEqual(x.bigint.parse('1'), { success: true, value: 1n })
assert.deepEqual(x.bigint.parse(1n), { success: true, value: 1n })
assert.deepEqual(x.bigint.parse(true), { success: true, value: 1n })
assert(x.bigint.parse(1.54).success === false)
assert(x.bigint.parse(1.23).success === false)
assert(x.bigint.parse({}).success === false)x.bigint.greaterThan
const schema = x.bigint.greaterThan(42n)
assert(schema.parse(43n).success === true)
assert(schema.parse(42n).success === false)x.bigint.lowerThan
const schema = x.bigint.lowerThan(42n)
assert(schema.parse(41n).success === true)
assert(schema.parse(42n).success === false)x.bigint.max
const schema = x.bigint.max(42n)
assert(schema.parse(42n).success === true)
assert(schema.parse(43n).success === false)x.bigint.min
const schema = x.bigint.min(10n)
assert(schema.parse(10n).success === true)
assert(schema.parse(9n).success === false)x.boolean
const schema = x.boolean
assert(x.boolean.parse(true).success === true)
assert(x.boolean.parse(false).success === true)
assert(x.boolean.parse(1).success === false)
assert(x.boolean.parse('toto').success === false)x.coercedInteger
assert(x.coercedInteger.parse('42').value === 42)
assert(x.coercedInteger.parse(true).value === 1)
assert(x.coercedInteger.parse(false).value === 0)
assert(x.coercedInteger.parse('42.2').success === false)
assert(x.coercedInteger.parse('abc').success === false)
assert(x.coercedInteger.parse({}).success === false)x.coercedNumber
assert(x.coercedNumber.parse('42').value === 42)
assert(x.coercedNumber.parse('42.2').value === 42.2)
assert(x.coercedNumber.parse(true).value === 1)
assert(x.coercedNumber.parse(false).value === 0)
assert(x.coercedNumber.parse('abc').success === false)
assert(x.coercedNumber.parse({}).success === false)x.date
It can parse anything the Date constructor can take as single parameter.
If you need to accept Date only, use x.instanceOf(Date)
parses a Date
const now = new Date()
assert.deepEqual(x.date.parse(now), { success: true, value: now })parses a string
const a = '2021-01-02T03:04:05.123Z'
assert.deepEqual(x.date.parse(a), { success: true, value: new Date(a) })
const b = '2021-01-02'
assert.deepEqual(x.date.parse(b), { success: true, value: new Date(b) })
assert(x.date.parse('oopsie').success === false)parses a number
const timestamp = Date.now()
assert.deepEqual(x.date.parse(timestamp), {
success: true,
value: new Date(timestamp),
})
assert(x.date.parse(NaN).success === false)
assert(x.date.parse(() => {}).success === false)x.date.greaterThan
const schema = x.date.greaterThan(new Date(2025))
assert(schema.parse(new Date(2025, 1)).success === true)
assert(schema.parse(new Date(2025)).success === false)x.date.lowerThan
const schema = x.date.lowerThan(new Date(2025))
assert(schema.parse(new Date(2024)).success === true)
assert(schema.parse(new Date(2025)).success === false)x.date.max
const schema = x.date.max(new Date(2025))
assert(schema.parse(new Date(2025)).success === true)
assert(schema.parse(new Date(2025, 0, 1)).success === false)x.date.min
const schema = x.date.min(new Date(2025))
assert(schema.parse(new Date(2025)).success === true)
assert(schema.parse(new Date(2024)).success === false)x.Enum
Parses as-const enum
const Direction = { Left: 'Left', Right: 'Right' } as const
const schema = x.Enum(Direction)
assert(schema.parse('Left').success === true)
assert(schema.parse('Left').value === Direction.Left)
assert(schema.parse('Letf').success === false)Parses an enum with values
enum Direction {
Left = 'Left',
Right = 'Right',
}
const schema = x.Enum(Direction)
assert(schema.parse('Left').success === true)
assert(schema.parse('Left').value === Direction.Left)
assert(schema.parse('Letf').success === false)Parses an enum without values
enum Direction {
Left,
Right,
}
const schema = x.Enum(Direction)
assert(schema.parse(0).success === true)
assert(schema.parse(0).value === Direction.Left)
assert(schema.parse(-1).success === false)x.fromGuard
Utility to create schemas.
Used to create most of the primitives.
Can be used to create custom schemas like Email.
rebuilding x.string
const string = x.fromGuard('string', (input) => typeof input === 'string')creating an email type
type Email = string & { _tag: 'Email' }
declare const isEmail: (input: unknown) => input is Email
const email = x.fromGuard('Email', isEmail)x.instanceOf
parsing a Date
const schema = x.instanceOf(Date)
assert(schema.parse(new Date()).success === true)parsing a custom User class
class User {}
const schema = x.instanceOf(User)
assert(schema.parse(new User()).success === true)
assert(schema.parse({}).success === false)usage with convertTo
class User {
constructor(public name: string) {}
}
const schema = x.string.convertTo(x.instanceOf(User), (name) => new User(name))
const result = schema.parse('Jack')
assert(result.success === true)
assert(result.value instanceof User)
assert(result.value.name === 'Jack')x.integer
it accepts anything passing the check Number.isSafeInteger.
assert(x.integer.parse(42).success === true)
assert(x.integer.parse(-42).success === true)
assert(x.integer.parse(31.2).success === false)
assert(x.integer.parse(Infinity).success === false)
assert(x.integer.parse(NaN).success === false)
assert(x.integer.parse(1e100).success === false)
assert(x.integer.parse(true).success === false)
assert(x.integer.parse('abc').success === false)x.integer.greaterThan
const schema = x.number.greaterThan(42)
assert(schema.parse(43).success === true)
assert(schema.parse(42).success === false)x.integer.lowerThan
const schema = x.number.lowerThan(42)
assert(schema.parse(41).success === true)
assert(schema.parse(42).success === false)x.integer.max
const schema = x.number.max(42)
assert(schema.parse(42).success === true)
assert(schema.parse(43).success === false)x.integer.min
const schema = x.number.min(42)
assert(schema.parse(42).success === true)
assert(schema.parse(41).success === false)x.literal
const schema = x.literal('a', 42, true, null, undefined)
assert(schema.parse('a').value === 'a')
assert(schema.parse(42).value === 42)
assert(schema.parse(true).value === true)
assert(schema.parse(null).value === null)
assert(schema.parse(undefined).value === undefined)
assert(schema.parse('b').success === false)
assert(schema.parse(43).success === false)
assert(schema.parse(false).success === false)x.Map
Parses any iterable of entries to a Map
NB: this schema is PascalCase to avoid confusion with mapper functions, like […].map(…)
const schema = x.Map(x.number, x.string)
const entries = [
[1, 'Jack'],
[2, 'Mary'],
]
const map = new Map(entries)
assert.deepEqual(
schema.parse(entries), // it parses entries
{ success: true, value: map },
)
assert.deepEqual(
schema.parse(map), // it parses a map
{ success: true, value: map },
)
assert(schema.parse([['1', 'Jack']]).success === false)
assert(schema.parse([['Jack', 1]]).success === false)x.Map.defaultMaxSize
Allows to configure the default max size of Maps.
The default value is intentionally low because safety-first.
If you need to increase it, I recommend increasing it locally at schema level: x.Map(x.string, x.string).size({ max: 10_000 })
If you need to loosen it globally, use x.Map.defaultMaxSize = 10_000
import { mapOfSize } from './test-utils'
x.Map.defaultMaxSize = 20
const schema = x.Map(x.string, x.string)
assert(schema.parse(mapOfSize(20)).success === true)
assert(schema.parse(mapOfSize(21)).success === false)x.Map.size
const schema = x.Map(x.number, x.number).size({ min: 1, max: 3 })
assert(schema.parse(new Map([[1, 1]])).success === true)
assert(schema.parse(new Map()).success === false)
assert(
schema.parse(
new Map([
[1, 1],
[2, 2],
[3, 3],
[4, 4],
]),
).success === false,
)x.number
This schema only accepts finite numbers for safety.
If you need full control over your number, use unsafeNumber instead.
Basically, it accepts anything passing the check Number.isFinite.
assert(x.number.parse(1).success === true)
assert(x.number.parse(-1.12).success === true)
assert(x.number.parse(Infinity).success === false)
assert(x.number.parse(NaN).success === false)
assert(x.number.parse(true).success === false)
assert(x.number.parse('abc').success === false)x.number.greaterThan
const schema = x.number.greaterThan(42)
assert(schema.parse(43).success === true)
assert(schema.parse(42).success === false)x.number.lowerThan
const schema = x.number.lowerThan(42)
assert(schema.parse(41).success === true)
assert(schema.parse(42).success === false)x.number.max
const schema = x.number.max(42)
assert(schema.parse(42).success === true)
assert(schema.parse(43).success === false)x.number.min
const schema = x.number.min(42)
assert(schema.parse(42).success === true)
assert(schema.parse(41).success === false)x.object
See x.typed<MyType>().object(…) to use a Type-Driven approach
const person = x.object({ name: x.string })
assert(person.parse({ name: 'Jack' }).success === true)
assert(person.parse({ name: 42 }).success === false)it parses null prototype objects
const schema = x.object({ n: x.number })
const a = Object.create(null)
a.n = 1
assert(schema.parse(a).success === true)
assert(schema.parse({ __proto__: null, n: 1 }).success === true)
assert(schema.parse([]).success === false)
assert(schema.parse('abc').success === false)
assert(schema.parse(42).success === false)
assert(schema.parse(new Set()).success === false)
assert(schema.parse(new Map()).success === false)x.record
const schema = x.record(x.string.convertTo(x.number, Number), x.string)
assert.deepEqual(schema.parse({ 42: 'hello' }), {
success: true,
value: { 42: 'hello' },
})
assert(schema.parse({ hello: 42 }).success === false)
assert(schema.parse({ hello: 'world' }).success === false)failures
const schema = x.record(x.string, x.number)
assert(schema.parse([]).success === false)
assert(schema.parse(new Set()).success === false)
assert(schema.parse(new Map()).success === false)
assert(schema.parse({ 1: '12' }).success === false)x.Set
Parses any iterable to a Set.
NB: this schema is PascalCase to avoid confusion with setter functions, like new Map(…).set(…)
const schema = x.Set(x.string)
assert.deepEqual(schema.parse(['a']).value, new Set(['a']))
assert.deepEqual(schema.parse(new Set(['a'])).value, new Set(['a']))
assert(schema.parse([1]).success === false)
assert(schema.parse(1).success === false)
assert(schema.parse({}).success === false)Access the Set content schema with .item
const schema = x.Set(x.string)
assert(schema.item === x.string)x.Set.defaultMaxSize
Allows to configure the default max size of Sets.
The default value is intentionally low because safety-first.
If you need to increase it, I recommend increasing it locally at schema level: x.Set(x.string).size({ max: 10_000 })
If you need to loosen it globally, use x.Set.defaultMaxSize = 10_000
import { setOfSize } from './test-utils'
x.Set.defaultMaxSize = 20
const schema = x.Set(x.string)
assert(schema.parse(setOfSize(20)).success === true)
assert(schema.parse(setOfSize(21)).success === false)x.Set.size
const schema = x.Set(x.number).size({ min: 1, max: 3 })
assert(schema.parse(new Set([1])).success === true)
assert(schema.parse(new Set()).success === false)
assert(schema.parse(new Set([1, 2, 3, 4])).success === false)x.string
This also trims the string. If you do not want this behavior, explicitly use x.untrimmedString
assert(x.string.parse(' hello ').value === 'hello')x.string.pattern
accepting only alpha letters
const schema = x.string.pattern(/^[a-zA-Z]+$/)
assert(schema.parse('abc').success === true)
assert(schema.parse('ab3').success === false)x.string.size
const description = 'short first name'
const schema = x.string.size({ min: 3, max: 5, description })
// or x.array(…), x.Set(…), x.Map(…)
assert(schema.parse('hey').success === true)
assert(schema.parse('he').success === false)
assert(schema.parse('hey yo').success === false)
assert(schema.refinements.size.description === description)x.tuple
const schema = x.tuple(x.string, x.number)
assert.deepEqual(schema.parse(['a', 1]), { success: true, value: ['a', 1] })
assert.deepEqual(schema.parse(['a', 1, 2, 3, 4, 5]), {
success: true,
value: ['a', 1],
})
assert(schema.parse([1, 2]).success === false)
assert(schema.items[0] === x.string)
assert(schema.items[1] === x.number)failures
const schema = x.tuple(x.string, x.number)
assert(schema.parse(['1']).success === false)
assert(schema.parse(new Set(['1', 2])).success === false)
assert(schema.parse(new Map()).success === false)
assert(schema.parse({ 0: '1', 1: 2 }).success === false)
assert(schema.parse({ 0: '1', 1: 2, length: 2 }).success === false)x.typed
This is unhoax’s Type-Driven API
x.typed.object
with a Person interface
interface Person {
name: string
}
const person = x.typed<Person>().object({ name: x.string })
assert(person.parse({ name: 'Jack' }).success === true)
assert(person.parse({ name: 42 }).success === false)
assert(person.name === 'object')x.union
If you want to use a discriminated union, checkout
const schema = x.union(x.string, x.number)
assert(schema.parse('a').value === 'a')
assert(schema.parse(42).value === 42)
assert(schema.parse({}).success === false)x.unknown
x.unknown will never fail and always cast the input as unknown.
x.unsafeInteger
it accepts anything passing the check Number.isInteger (not Number.isSafeInteger).
assert(x.unsafeInteger.parse(42).success === true)
assert(x.unsafeInteger.parse(-42).success === true)
assert(x.unsafeInteger.parse(1e100).success === true)
assert(x.unsafeInteger.parse(31.2).success === false)
assert(x.unsafeInteger.parse(Infinity).success === false)
assert(x.unsafeInteger.parse(NaN).success === false)
assert(x.unsafeInteger.parse(true).success === false)
assert(x.unsafeInteger.parse('abc').success === false)x.unsafeInteger.greaterThan
const schema = x.number.greaterThan(42)
assert(schema.parse(43).success === true)
assert(schema.parse(42).success === false)x.unsafeInteger.lowerThan
const schema = x.number.lowerThan(42)
assert(schema.parse(41).success === true)
assert(schema.parse(42).success === false)x.unsafeInteger.max
const schema = x.number.max(42)
assert(schema.parse(42).success === true)
assert(schema.parse(43).success === false)x.unsafeInteger.min
const schema = x.number.min(42)
assert(schema.parse(42).success === true)
assert(schema.parse(41).success === false)x.unsafeNumber
⚠️ valid inputs are Infinity, NaN and unsafe integers.
Basically, anything which passes the check typeof x = 'number'.
assert(x.unsafeNumber.parse(1).success === true)
assert(x.unsafeNumber.parse(-1.12).success === true)
assert(x.unsafeNumber.parse(Infinity).success === true)
assert(x.unsafeNumber.parse(NaN).success === true)
assert(x.unsafeNumber.parse(true).success === false)
assert(x.unsafeNumber.parse('abc').success === false)x.unsafeNumber.greaterThan
const schema = x.number.greaterThan(42)
assert(schema.parse(43).success === true)
assert(schema.parse(42).success === false)x.unsafeNumber.lowerThan
const schema = x.number.lowerThan(42)
assert(schema.parse(41).success === true)
assert(schema.parse(42).success === false)x.unsafeNumber.max
const schema = x.number.max(42)
assert(schema.parse(42).success === true)
assert(schema.parse(43).success === false)x.unsafeNumber.min
const schema = x.number.min(42)
assert(schema.parse(42).success === true)
assert(schema.parse(41).success === false)x.untrimmedString
assert(x.untrimmedString.parse(' hello ').value === ' hello ')x.untrimmedString.pattern
accepting only alpha letters
const schema = x.string.pattern(/^[a-zA-Z]+$/)
assert(schema.parse('abc').success === true)
assert(schema.parse('ab3').success === false)x.untrimmedString.size
const description = 'short first name'
const schema = x.string.size({ min: 3, max: 5, description })
// or x.array(…), x.Set(…), x.Map(…)
assert(schema.parse('hey').success === true)
assert(schema.parse('he').success === false)
assert(schema.parse('hey yo').success === false)
assert(schema.refinements.size.description === description)x.variant
If you need to use a simple union, checkout
const a = x.object({ type: x.literal('a'), a: x.string })
const b = x.object({ type: x.literal('b'), b: x.number })
const schema = x.variant('type', [a, b])
assert.deepEqual(schema.parse({ type: 'a', a: 'Hello' }), {
success: true,
value: { type: 'a', a: 'Hello' },
})
assert.deepEqual(schema.parse({ type: 'b', b: 42 }), {
success: true,
value: { type: 'b', b: 42 },
})
assert(schema.name === 'a | b')