$ npm install io-ts
A value of type Type<S, A>
(called "runtime type") is the runtime representation of the static type A
.
Also a runtime type can
validate
)serialize
)is
)export type mixed = object | number | string | boolean | symbol | undefined | null
class Type<S, A> {
readonly _A: A
readonly _S: S
constructor(
/** a unique name for this runtime type */
readonly name: string,
/** a custom type guard */
readonly is: (v: mixed) => v is A,
/** succeeds if a value of type S can be decoded to a value of type A */
readonly validate: (input: S, context: Context) => Either<Errors, A>,
/** converts a value of type A to a value of type S */
readonly serialize: (output: A) => S
) {}
}
Note. The Either
type is defined in fp-ts, a library containing implementations of
common algebraic types in TypeScript.
Example
A runtime type representing string
can be defined as
import * as t from 'io-ts'
export class StringType extends Type<mixed, string> {
constructor() {
super(
'string',
(v): v is string => typeof v === 'string',
(s, c) => (this.is(s) ? success(s) : failure(s, c)),
a => a
)
}
}
A runtime type can be used to validate an object in memory (for example an API payload)
const Person = t.interface({
name: t.string,
age: t.number
})
// ok
t.validate(JSON.parse('{"name":"Giulio","age":43}'), Person) // => Right({name: "Giulio", age: 43})
// ko
t.validate(JSON.parse('{"name":"Giulio"}'), Person) // => Left([...])
A reporter implements the following interface
interface Reporter<A> {
report: (validation: Validation<any>) => A;
}
This package exports two default reporters
PathReporter: Reporter<Array<string>>
ThrowReporter: Reporter<void>
Example
import { PathReporter } from 'io-ts/lib/PathReporter'
import { ThrowReporter } from 'io-ts/lib/ThrowReporter'
const validation = t.validate({ name: 'Giulio' }, Person)
console.log(PathReporter.report(validation))
// => ['Invalid value undefined supplied to : { name: string, age: number }/age: number']
ThrowReporter.report(validation)
// => throws 'Invalid value undefined supplied to : { name: string, age: number }/age: number'
Runtime types can be inspected
This library uses TypeScript extensively. Its API is defined in a way which automatically infers types for produced values
Note that the type annotation isn't needed, TypeScript infers the type automatically based on a schema.
Static types can be extracted from runtime types with the TypeOf
operator
type IPerson = t.TypeOf<typeof Person>
// same as
type IPerson = {
name: string,
age: number
}
Recursive types can't be inferred by TypeScript so you must provide the static type as a hint
// helper type
type ICategory = {
name: string,
categories: Array<ICategory>
}
const Category =
t.recursion <
ICategory >
('Category',
self =>
t.interface({
name: t.string,
categories: t.array(self)
}))
import * as t from 'io-ts'
Type | TypeScript | Flow | Runtime type / combinator |
---|---|---|---|
null | null |
null |
t.null or t.nullType |
undefined | undefined |
void |
t.undefined |
string | string |
string |
t.string |
number | number |
number |
t.number |
boolean | boolean |
boolean |
t.boolean |
any | any |
any |
t.any |
never | never |
empty |
t.never |
object | object |
✘ | t.object |
integer | ✘ | ✘ | t.Integer |
array of any | Array<mixed> |
Array<mixed> |
t.Array |
array of type | Array<A> |
Array<A> |
t.array(A) |
dictionary of any | { [key: string]: mixed } |
{ [key: string]: mixed } |
t.Dictionary |
dictionary of type | { [K in A]: B } |
{ [key: A]: B } |
t.dictionary(A, B) |
function | Function |
Function |
t.Function |
literal | 's' |
's' |
t.literal('s') |
partial | Partial<{ name: string }> |
$Shape<{ name: string }> |
t.partial({ name: t.string }) |
readonly | Readonly<T> |
ReadOnly<T> |
t.readonly(T) |
readonly array | ReadonlyArray<number> |
ReadOnlyArray<number> |
t.readonlyArray(t.number) |
interface | interface A { name: string } |
interface A { name: string } |
t.interface({ name: t.string }) or t.type({ name: t.string }) |
interface inheritance | interface B extends A {} |
interface B extends A {} |
t.intersection([ A, t.interface({}) ]) |
tuple | [ A, B ] |
[ A, B ] |
t.tuple([ A, B ]) |
union | A \| B |
A \| B |
t.union([ A, B ]) or t.taggedUnion(tag, [ A, B ]) |
intersection | A & B |
A & B |
t.intersection([ A, B ]) |
keyof | keyof M |
$Keys<M> |
t.keyof(M) |
recursive types | see Recursive types | see Recursive types | t.recursion(name, definition) |
refinement | ✘ | ✘ | t.refinement(A, predicate) |
strict/exact types | ✘ | $Exact<{{ name: t.string }}> |
t.strict({ name: t.string }) |
If you are encoding tagged unions, instead of the general purpose union
combinator, you may want to use the
taggedUnion
combinator in order to get better performances
const A = t.type({
tag: t.literal('A'),
foo: t.string
})
const B = t.type({
tag: t.literal('B'),
bar: t.number
})
// the actual presence of the tag is statically checked
const U = t.taggedUnion('tag', [A, B])
You can refine a type (any type) using the refinement
combinator
const Positive = t.refinement(t.number, n => n >= 0, 'Positive')
const Adult = t.refinement(Person, person => person.age >= 18, 'Adult')
You can make an interface strict (which means that only the given properties are allowed) using the strict
combinator
const Person = t.interface({
name: t.string,
age: t.number
})
const StrictPerson = t.strict(Person.props)
t.validate({ name: 'Giulio', age: 43, surname: 'Canti' }, Person) // ok
t.validate({ name: 'Giulio', age: 43, surname: 'Canti' }, StrictPerson) // fails
Note. You can mix required and optional props using an intersection
const A = t.interface({
foo: t.string
})
const B = t.partial({
bar: t.number
})
const C = t.intersection([A, B])
type CT = t.TypeOf<typeof C>
// same as
type CT = {
foo: string
bar?: number
}
You can define a custom combinator to avoid the boilerplate
export function interfaceWithOptionals<R extends t.Props, O extends t.Props>(
required: R,
optional: O,
name?: string
): t.IntersectionType<
[t.InterfaceType<R, t.InterfaceOf<R>>, t.PartialType<O, t.PartialOf<O>>],
t.InterfaceOf<R> & t.PartialOf<O>
> {
return t.intersection([t.interface(required), t.partial(optional)], name)
}
const C = interfaceWithOptionals({ foo: t.string }, { bar: t.number })
You can define your own types. Let's see an example
import * as t from 'io-ts'
// represents a Date from an ISO string
const DateFromString = new t.Type<t.mixed, Date>(
'DateFromString',
(v): v is Date => v instanceof Date,
(v, c) =>
t.string.validate(v, c).chain(s => {
const d = new Date(s)
return isNaN(d.getTime()) ? t.failure(s, c) : t.success(d)
}),
a => a.toISOString()
)
const s = new Date(1973, 10, 30).toISOString()
t.validate(s, DateFromString)
// right(new Date('1973-11-29T23:00:00.000Z'))
t.validate('foo', DateFromString)
// left(errors...)
Note that you can deserialize while validating.
You can define your own combinators. Let's see some examples
maybe
combinatorAn equivalent to T | null
export function maybe<RT extends t.Any>(type: RT, name?: string): t.UnionType<[RT, t.NullType], t.TypeOf<RT> | null> {
return t.union<[RT, t.NullType]>([type, t.null], name)
}
pluck
combinatorExtracting the runtime type of a field contained in each member of a union
const pluck = <F extends string, U extends t.UnionType<Array<t.InterfaceType<{ [K in F]: t.Any }, any>>, any>>(
union: U,
field: F
): t.Type<t.mixed, t.TypeOf<U>[F]> => {
return t.union(union.types.map(type => type.props[field]))
}
export const Action = t.union([
t.interface({
type: t.literal('Action1'),
payload: t.interface({
foo: t.string
})
}),
t.interface({
type: t.literal('Action2'),
payload: t.interface({
bar: t.string
})
})
])
// ActionType: t.Type<t.mixed, "Action1" | "Action2">
const ActionType = pluck(Action, 'type')
No, however you can define your own logic for that (if you really trust the input and the involved types don't perform deserializations)
import * as t from 'io-ts'
import { failure } from 'io-ts/lib/PathReporter'
const { NODE_ENV } = process.env
export function unsafeValidate<S, A>(value: any, type: t.Type<S, A>): A {
if (NODE_ENV !== 'production') {
return t.validate(value, type).getOrElse(errors => {
throw new Error(failure(errors).join('\n'))
})
}
// unsafe cast
return value as A
}
Due to an upstream bug, VS Code might display weird types for nested interfaces
const NestedInterface = t.interface({
foo: t.interface({
bar: t.string
})
})
type NestedInterfaceType = t.TypeOf<typeof NestedInterface>
/*
Hover on NestedInterfaceType will display
type NestedInterfaceType = {
foo: t.InterfaceOf<{
bar: t.StringType;
}>;
}
instead of
type NestedInterfaceType = {
foo: {
bar: string;
};
}
*/
© 2010 - cnpmjs.org x YWFE | Home | YWFE