Built-in decoders
All βbatteries includedβ decoders available in the standard library.
- Strings:
string
,nonEmptyString
,regex()
,startsWith()
,endsWith()
,decimal
,hexadecimal
,numeric
,email
,url
,httpsUrl
,identifier
,nanoid
,uuid
,uuidv1
,uuidv4
- Numbers:
number
,integer
,positiveNumber
,positiveInteger
,anyNumber
,bigint
- Booleans:
boolean
,truthy
- Dates:
date
,iso8601
,datelike
- Constants:
constant()
,always()
,hardcoded()
- Optionality:
null_
,undefined_
,optional()
,nullable()
,nullish()
,unknown
,maybe()
,mixed
- Arrays:
array()
,nonEmptyArray()
,poja
,tuple()
- Objects:
object()
,exact()
,inexact()
,pojo
- Collections:
record()
,dict()
,mapping()
,setFromArray()
,set()
- JSON values:
json
,jsonObject
,jsonArray
- Unions:
either()
,oneOf()
,enum_()
,taggedUnion()
,select()
- Utilities:
define()
,prep()
,never
,instanceOf()
,lazy()
,fail
Strings
string
nonEmptyString
regex()
startsWith()
endsWith()
decimal
hexadecimal
numeric
email
url
httpsUrl
identifier
nanoid
uuid
uuidv1
uuidv4
# string: Decoder<string> (source)
Accepts and returns strings.
// π
string.verify('hello world') === 'hello world';
string.verify('π') === 'π';
string.verify('') === '';
// π
string.verify(123); // throws
string.verify(true); // throws
string.verify(null); // throws
# nonEmptyString: Decoder<string> (source)
Like string
, but will reject the empty string or strings containing only whitespace.
// π
nonEmptyString.verify('hello world') === 'hello world';
nonEmptyString.verify('π') === 'π';
// π
nonEmptyString.verify(123); // throws
nonEmptyString.verify(' '); // throws
nonEmptyString.verify(''); // throws
# regex(pattern: RegExp, message: string): Decoder<string> (source)
Accepts and returns strings that match the given regular expression.
const decoder = regex(/^[0-9][0-9]+$/, 'Must be numeric');
// π
decoder.verify('42') === '42';
decoder.verify('83401648364738') === '83401648364738';
// π
decoder.verify(''); // throws
decoder.verify('1'); // throws
decoder.verify('foo'); // throws
# startsWith(prefix: P): Decoder<`${P}${string}`> (source)
Accepts and returns strings that start with the given prefix.
const decoder = startsWith('abc');
// π
decoder.verify('abc') === 'abc';
decoder.verify('abcdefg') === 'abcdefg';
// π
decoder.verify(42); // throws
decoder.verify('ab'); // throws
decoder.verify('ABC'); // throws
# endsWith(suffix: S): Decoder<`${string}${S}`> (source)
Accepts and returns strings that end with the given suffix.
const decoder = endsWith('bar');
// π
decoder.verify('bar') === 'bar';
decoder.verify('foobar') === 'foobar';
// π
decoder.verify(42); // throws
decoder.verify('Bar'); // throws
decoder.verify('bark'); // throws
# decimal: Decoder<string> (source)
Accepts and returns strings with decimal digits only (base-10). To convert these to numbers, use the numeric
decoder.
const decoder = decimal;
// π
decoder.verify('42') === '42';
decoder.verify('83401648364738') === '83401648364738';
// π
decoder.verify(''); // throws
decoder.verify('123abc'); // throws
decoder.verify('foo'); // throws
decoder.verify(123); // throws (not a string)
# hexadecimal: Decoder<string> (source)
Accepts and returns strings with hexadecimal digits only (base-16).
const decoder = hexadecimal;
// π
decoder.verify('0123456789ABCDEF') === '0123456789ABCDEF';
decoder.verify('deadbeef') === 'deadbeef';
// π
decoder.verify('abcdefghijklm'); // throws (not hexadecimal)
decoder.verify(''); // throws
decoder.verify('1'); // throws
# numeric: Decoder<number> (source)
Accepts valid numerical strings (in base-10) and returns them as a number. To only accept numerical strings and keep them as string values, use the decimal
decoder.
const decoder = numeric;
// π
decoder.verify('42') === 42;
decoder.verify('83401648364738') === 83401648364738;
// π
decoder.verify(''); // throws
decoder.verify('123abc'); // throws
decoder.verify('foo'); // throws
decoder.verify(123); // throws (not a string)
# email: Decoder<string> (source)
Accepts and returns strings that are syntactically valid email addresses. (This will not mean that the email address actually exist.)
// π
email.verify('alice@acme.org') === 'alice@acme.org';
// π
email.verify('foo'); // throws
email.verify('@acme.org'); // throws
email.verify('alice @ acme.org'); // throws
Accepts strings that are valid URLs, returns the value as a URL instance.
// π
url.verify('http://nvie.com') === new URL('http://nvie.com/');
url.verify('https://nvie.com') === new URL('https://nvie.com/');
url.verify('git+ssh://user@github.com/foo/bar.git') === new URL('git+ssh://user@github.com/foo/bar.git');
// π
url.verify('foo'); // throws
url.verify('@acme.org'); // throws
url.verify('alice @ acme.org'); // throws
url.verify('/search?q=foo'); // throws
# httpsUrl: Decoder<URL> (source)
Accepts strings that are valid URLs, but only HTTPS ones. Returns the value as a URL instance.
// π
httpsUrl.verify('https://nvie.com:443') === new URL('https://nvie.com/');
// π
httpsUrl.verify('http://nvie.com'); // throws, not HTTPS
httpsUrl.verify('git+ssh://user@github.com/foo/bar.git'); // throws, not HTTPS
Tip! If you need to limit URLs to different protocols than HTTP, you can do as the HTTPS decoder is implemented: by adding further conditions using a .refine()
call.
import { url } from 'decoders';
const gitUrl: Decoder<URL> = url.refine(
(value) => value.protocol === 'git:',
'Must be a git:// URL',
);
# identifier: Decoder<string> (source)
Accepts and returns strings that are valid identifiers in most programming languages.
// π
identifier.verify('x') === 'x'
identifier.verify('abc123') === 'abc123'
identifier.verify('_123') === '_123'
identifier.verify('a_b_c_1_2_3') === 'a_b_c_1_2_3'
// π
identifier.verify('123xyz'); // cannot start with digit
identifier.verify('x-y'); // invalid chars
identifier.verify('!@#$%^&*()=+'); // invalid chars
identifier.verify('π€―'); // invalid chars
identifier.verify(42); // not a string
# nanoid: Decoder<string> (source)
Accepts and returns nanoid string values. It assumes the default nanoid alphabet. If youβre using a custom alphabet, use regex()
instead.
// π
nanoid().verify('1-QskICa3CaPGcKuYYTm1') === '1-QskICa3CaPGcKuYYTm1'
nanoid().verify('vA4mt7CUWnouU6jTGbMP_') === 'vA4mt7CUWnouU6jTGbMP_'
nanoid({ size: 7 }).verify('yH8mx-7') === 'yH8mx-7'
nanoid({ min: 7, max: 10 }).verify('yH8mx-7') === 'yH8mx-7'
nanoid({ min: 7, max: 10 }).verify('yH8mx-7890') === 'yH8mx-7890'
// π
nanoid().verify('123E4567E89B12D3A456426614174000'); // too long
nanoid().verify('abcdefghijkl'); // too short
nanoid().verify('$*&(#%*&('); // invalid chars
# uuid: Decoder<string> (source)
Accepts strings that are valid UUIDs (universally unique identifier).
// π
uuid.verify('123e4567-e89b-12d3-a456-426614174000') === '123e4567-e89b-12d3-a456-426614174000'
uuid.verify('123E4567-E89B-12D3-A456-426614174000') === '123E4567-E89B-12D3-A456-426614174000'
// π
uuid.verify('123E4567E89B12D3A456426614174000'); // throws
uuid.verify('abcdefgh-ijkl-mnop-qrst-uvwxyz012345'); // throws
# uuidv1: Decoder<URL> (source)
Like uuid
, but only accepts UUIDv1 strings.
// π
uuidv1.verify('123e4567-e89b-12d3-a456-426614174000') === '123e4567-e89b-42d3-a456-426614174000'
// π
uuidv1.verify('123e4567-e89b-42d3-a456-426614174000') // throws
# uuidv4: Decoder<URL> (source)
Like uuid
, but only accepts UUIDv4 strings.
// π
uuidv4.verify('123e4567-e89b-42d3-a456-426614174000') === '123e4567-e89b-42d3-a456-426614174000'
// π
uuidv4.verify('123e4567-e89b-12d3-a456-426614174000') // throws
Numbers
# number: Decoder<number> (source)
Accepts finite numbers (can be integer or float values). Values NaN
, or positive and negative Infinity
will get rejected.
// π
number.verify(123) === 123;
number.verify(-3.14) === -3.14;
// π
number.verify(Infinity); // throws
number.verify(NaN); // throws
number.verify('not a number'); // throws
# integer: Decoder<number> (source)
Accepts only finite whole numbers.
// π
integer.verify(123) === 123;
// π
integer.verify(-3.14); // throws
integer.verify(Infinity); // throws
integer.verify(NaN); // throws
integer.verify('not a integer'); // throws
# positiveNumber: Decoder<number> (source)
Accepts only non-negative (zero or positive) finite numbers.
// π
positiveNumber.verify(123) === 123;
positiveNumber.verify(3.14) === 3.14;
positiveNumber.verify(0) === 0;
// π
positiveNumber.verify(-42); // throws
positiveNumber.verify(Infinity); // throws
positiveNumber.verify(NaN); // throws
positiveNumber.verify('not a number'); // throws
positiveNumber.verify(-0); // throws
# positiveInteger: Decoder<number> (source)
Accepts only non-negative (zero or positive) finite whole numbers.
// π
positiveInteger.verify(123) === 123;
positiveInteger.verify(0) === 0;
// π
positiveInteger.verify(-3); // throws
positiveInteger.verify(3.14); // throws
positiveInteger.verify(Infinity); // throws
positiveInteger.verify(NaN); // throws
positiveInteger.verify('not a number'); // throws
positiveInteger.verify(-0); // throws
# anyNumber: Decoder<number> (source)
Accepts any valid number
value.
This also accepts special values like NaN
and Infinity
. Unless you want to deliberately accept those, youβll likely want to use the number
decoder instead.
// π
anyNumber.verify(123) === 123;
anyNumber.verify(-3.14) === -3.14;
anyNumber.verify(Infinity) === Infinity;
anyNumber.verify(NaN) === NaN;
// π
anyNumber.verify('not a number'); // throws
# bigint: Decoder<bigint> (source)
Accepts any valid bigint
value.
// π
bigint.verify(123n) === 123n;
bigint.verify(-4543000000n) === -4543000000n;
// π
bigint.verify(123); // throws
bigint.verify(-3.14); // throws
bigint.verify(Infinity); // throws
bigint.verify(NaN); // throws
bigint.verify('not a number'); // throws
Booleans
# boolean: Decoder<boolean> (source)
Accepts and returns booleans.
// π
boolean.verify(false) === false;
boolean.verify(true) === true;
// π
boolean.verify(undefined); // throws
boolean.verify('hello world'); // throws
boolean.verify(123); // throws
# truthy: Decoder<boolean> (source)
Accepts anything and will return its βtruthβ value. Will never reject.
// π
truthy.verify(false) === false;
truthy.verify(true) === true;
truthy.verify(undefined) === false;
truthy.verify('hello world') === true;
truthy.verify('false') === true;
truthy.verify(0) === false;
truthy.verify(1) === true;
truthy.verify(null) === false;
// π
// This decoder will never reject an input
Dates
# date: Decoder<Date> (source)
Accepts and returns Date
instances.
const now = new Date();
// π
date.verify(now) === now;
// π
date.verify(123); // throws
date.verify('hello'); // throws
# iso8601: Decoder<Date> (source)
Accepts ISO8601-formatted strings, returns them as Date
instances.
This is very useful for working with dates in APIs: serialize them as .toISOString()
when sending, decode them with iso8601
when receiving.
// π
iso8601.verify('2020-06-01T12:00:00Z'); // new Date('2020-06-01T12:00:00Z')
// π
iso8601.verify('2020-06-01'); // throws
iso8601.verify('hello'); // throws
iso8601.verify(123); // throws
iso8601.verify(new Date()); // throws (does not accept dates)
# datelike: Decoder<Date> (source)
Accepts either a Date, or an ISO date string, returns a Date instance. This is commonly useful to build decoders that can be reused to validate object with Date instances as well as objects coming from JSON payloads.
// π
datelike.verify('2024-01-08T12:00:00Z'); // strings...
datelike.verify(new Date()); // ...or Date instances
// π
datelike.verify('2020-06-01'); // throws
datelike.verify('hello'); // throws
datelike.verify(123); // throws
Constants
constant()
always()
hardcoded()
(alias ofalways()
)
# constant<T>(value: T): Decoder<T> (source)
Accepts only the given constant value.
const decoder = constant('hello');
// π
decoder.verify('hello') === 'hello';
// π
decoder.verify('this breaks'); // throws
decoder.verify(false); // throws
decoder.verify(undefined); // throws
# always<T>(value: T): Decoder<T> (source)
# hardcoded<T>(value: T): Decoder<T> (source)
Accepts anything, completely ignores it, and always returns the provided value instead.
This is useful to manually add extra fields to object decoders.
const decoder = always(42);
// π
decoder.verify('hello') === 42;
decoder.verify(false) === 42;
decoder.verify(undefined) === 42;
// π
// This decoder will never reject an input
Or use it with a function instead of a constant:
const now = always(() => new Date());
now.verify('dummy'); // e.g. new Date('2022-02-07T09:36:58.848Z')
Optionality
null_
undefined_
optional()
nullable()
nullish()
unknown
maybe()
(alias ofnullish()
)mixed
(alias ofunknown
)
# null_: Decoder<null> (source)
Accepts and returns only the literal null
value.
// π
null_.verify(null) === null;
// π
null_.verify(false); // throws
null_.verify(undefined); // throws
null_.verify('hello world'); // throws
# undefined_: Decoder<undefined> (source)
Accepts and returns only the literal undefined
value.
// π
undefined_.verify(undefined) === undefined;
// π
undefined_.verify(null); // throws
undefined_.verify(false); // throws
undefined_.verify('hello world'); // throws
# optional<T>(decoder: Decoder<T>): Decoder<T | undefined> (source)
# optional<T, V>(decoder: Decoder<T>, defaultValue: V | (() => V)): Decoder<T | V> (source)
Accepts whatever the given decoder accepts, or undefined
.
If a default value is explicitly provided, return that instead in the undefined
case.
const decoder = optional(string);
// π
decoder.verify('hello') === 'hello';
decoder.verify(undefined) === undefined;
// π
decoder.verify(null); // throws
decoder.verify(0); // throws
decoder.verify(42); // throws
A typical case where optional()
is useful is in decoding objects with optional fields:
object({
id: number,
name: string,
address: optional(string),
});
Which will decode to type:
{
id: number;
name: string;
address?: string;
}
# nullable<T>(decoder: Decoder<T>): Decoder<T | null> (source)
# nullable<T, V>(decoder: Decoder<T>, defaultValue: V | (() => V)): Decoder<T | V> (source)
Accepts whatever the given decoder accepts, or null
.
If a default value is explicitly provided, return that instead in the null
case.
const decoder = nullable(string);
// π
decoder.verify('hello') === 'hello';
decoder.verify(null) === null;
// π
decoder.verify(undefined); // throws
decoder.verify(0); // throws
decoder.verify(42); // throws
Or use it with a default value:
const decoder = nullable(iso8601, () => new Date());
decoder.verify('2022-01-01T12:00:00Z') === '2022-01-01T12:00:00Z';
decoder.verify(null); // the current date
# nullish<T>(decoder: Decoder<T>): Decoder<T | null | undefined> (source)
# nullish<T, V>(decoder: Decoder<T>, defaultValue: V | (() => V)): Decoder<T | V> (source)
# maybe<T>(decoder: Decoder<T>): Decoder<T | null | undefined> (source)
# maybe<T, V>(decoder: Decoder<T>, defaultValue: V | (() => V)): Decoder<T | V> (source)
Accepts whatever the given decoder accepts, or null
, or undefined
.
If a default value is explicitly provided, return that instead in the null
/undefined
case.
const decoder = nullish(string);
// π
decoder.verify('hello') === 'hello';
decoder.verify(null) === null;
decoder.verify(undefined) === undefined;
// π
decoder.verify(0); // throws
decoder.verify(42); // throws
Or use it with a default value:
const decoder = nullish(string, null);
decoder.verify('hello') === 'hello';
decoder.verify(null) === null;
decoder.verify(undefined) === null;
# unknown: Decoder<unknown> (source)
# mixed: Decoder<unknown> (source)
Accepts anything and returns it unchanged.
Useful for situation in which you donβt know or expect a specific type. Of course, the downside is that you wonβt know the type of the value statically and youβll have to further refine it yourself.
// π
unknown.verify('hello') === 'hello';
unknown.verify(false) === false;
unknown.verify(undefined) === undefined;
unknown.verify([1, 2]) === [1, 2];
// π
// This decoder will never reject an input
Arrays
# array(decoder: Decoder<T>): Decoder<T[]> (source)
Accepts arrays of whatever the given decoder accepts.
const decoder = array(string);
// π
decoder.verify(['hello', 'world']) === ['hello', 'world'];
decoder.verify([]) === [];
// π
decoder.verify(['hello', 1.2]); // throws
# nonEmptyArray(decoder: Decoder<T>): Decoder<[T, β¦T[]]> (source)
Like array()
, but will reject arrays with 0 elements.
const decoder = nonEmptyArray(string);
// π
decoder.verify(['hello', 'world']) === ['hello', 'world'];
// π
decoder.verify(['hello', 1.2]); // throws
decoder.verify([]); // throws
# poja: Decoder<unknown[]> (source)
Accepts any array, but doesnβt validate its items further.
βpojaβ means βplain old JavaScript arrayβ, a play on pojo
.
// π
poja.verify([1, 'hi', true]) === [1, 'hi', true];
poja.verify(['hello', 'world']) === ['hello', 'world'];
poja.verify([]) === [];
// π
poja.verify({}); // throws
poja.verify('hi'); // throws
# tuple<A, B, β¦>(Decoder<A>, Decoder<B>, β¦): Decoder<[A, B, β¦]> (source)
Accepts a tuple (an array with exactly n items) of values accepted by the n given decoders.
const decoder = tuple(string, number);
// π
decoder.verify(['hello', 1.2]) === ['hello', 1.2];
// π
decoder.verify([]); // throws, too few items
decoder.verify(['hello', 'world']); // throws, not the right types
decoder.verify(['a', 1, 'c']); // throws, too many items
Objects
# object<A, B, β¦>({ field1: Decoder<A>, field2: Decoder<B>, β¦ }): Decoder<{ field1: A, field2: B, β¦ }> (source)
Accepts objects with fields matching the given decoders. Extra fields that exist on the input object are ignored and will not be returned.
const decoder = object({
x: number,
y: number,
});
// π
decoder.verify({ x: 1, y: 2 }) === { x: 1, y: 2 };
decoder.verify({ x: 1, y: 2, z: 3 }) === { x: 1, y: 2 }; // β οΈ extra field `z` not returned!
// π
decoder.verify({ x: 1 }); // throws, missing field `y`
For more information, see also The difference between object
, exact
, and inexact
.
# exact<A, B, β¦>({ field1: Decoder<A>, field2: Decoder<B>, β¦ }): Decoder<{ field1: A, field2: B, β¦ }> (source)
Like object()
, but will reject inputs that contain extra fields that are not specified explicitly.
const decoder = exact({
x: number,
y: number,
});
// π
decoder.verify({ x: 1, y: 2 }) === { x: 1, y: 2 };
// π
decoder.verify({ x: 1, y: 2, z: 3 }); // throws, extra field `z` not allowed
decoder.verify({ x: 1 }); // throws, missing field `y`
For more information, see also The difference between object
, exact
, and inexact
.
# inexact<A, B, β¦>({ field1: Decoder<A>, field2: Decoder<B>, β¦ }): Decoder<{ field1: A, field2: B, β¦ }> (source)
Like object()
, but will pass through any extra fields on the input object unvalidated that will thus be of unknown
type statically.
const decoder = inexact({
x: number,
y: number,
});
// π
decoder.verify({ x: 1, y: 2 }) === { x: 1, y: 2 };
decoder.verify({ x: 1, y: 2, z: 3 }) === { x: 1, y: 2, z: 3 };
// π
decoder.verify({ x: 1 }); // throws, missing field `y`
For more information, see also The difference between object
, exact
, and inexact
.
# pojo: Decoder<Record<string, unknown>> (source)
Accepts any βplain old JavaScript objectβ, but doesnβt validate its keys or values further.
// π
pojo.verify({}) === {};
pojo.verify({ name: 'hi' }) === { name: 'hi' };
// π
pojo.verify('hi'); // throws
pojo.verify([]); // throws
pojo.verify(new Date()); // throws
pojo.verify(null); // throws
Collections
# record<V>(values: Decoder<V>): Decoder<Record<string, V>> (source)
# record<K, V>(keys: Decoder<K>, values: Decoder<V>): Decoder<Record<K, V>> (source)
Accepts objects where all values match the given decoder, and returns the result as a Record<string, V>
.
This is useful to validate inputs like { [key: string]: V }
.
Decoding values only
The default call takes a single argument and will validate all values. For example, to validate that all values in the object are numbers:
const decoder = record(number);
// \
// Values must be numbers
// π
decoder.verify({ red: 1, blue: 2, green: 3 });
// π
decoder.verify({ hi: 'not a number' });
Decoding keys and values
If you also want to validate that keys are of a specific form, use the two-argument form: record(key, value)
. Note that the given key decoder must return strings.
For example, to enforce that all keys are emails:
const decoder = record(email, number);
// / \
// Keys must Values must
// be emails be numbers
// π
decoder.verify({ "me@nvie.com": 1 });
// π
decoder.verify({ "no-email": 1 });
# dict<V>(decoder: Decoder<T>): Decoder<Record<string, V>> (source)
Alias of record()
.
# mapping<T>(decoder: Decoder<T>): Decoder<Map<string, T>> (source)
Similar to record()
, but returns the result as a Map<string, T>
(an ES6 Map) instead.
const decoder = mapping(number);
// π
decoder.verify({ red: 1, blue: 2, green: 3 });
// Map([
// ['red', '1'],
// ['blue', '2'],
// ['green', '3'],
// ]);
# setFromArray<T>(decoder: Decoder<T>): Decoder<Set<T>> (source)
Similar to array()
, but returns the result as an ES6 Set.
const decoder = setFromArray(string);
// π
decoder.verify(['abc', 'pqr']) // new Set(['abc', 'pqr'])
decoder.verify([]) // new Set([])
// π
decoder.verify([1, 2]); // throws, not the right types
# set<T>(decoder: Decoder<T>): Decoder<Set<T>> (source)
An alias of setFromArray()
.
β οΈ IMPORTANT! This decoder will change its behavior in a future version! If you rely on this decoder, use setFromArray()
instead.
JSON values
# json: Decoder<JSONValue> (source)
Accepts any value thatβs a valid JSON value.
In other words: any value returned by JSON.parse()
should decode without failure.
type JSONValue =
| null
| string
| number
| boolean
| { [string]: JSONValue }
| JSONValue[]
// π
json.verify({
name: 'Amir',
age: 27,
admin: true,
image: null,
tags: ['vip', 'staff'],
});
# jsonObject: Decoder<{ [string]: JSONValue }> (source)
Accepts objects that contain only valid JSON values.
// π
jsonObject.verify({}); // {}
jsonObject.verify({ name: 'Amir' }); // { name: 'Amir' }
// π
jsonObject.verify([]); // throws
jsonObject.verify([{ name: 'Alice' }]); // throws
jsonObject.verify('hello'); // throws
jsonObject.verify(null); // throws
# jsonArray: Decoder<JSONValue[]> (source)
Accepts arrays that contain only valid JSON values.
// π
jsonArray.verify([]); // []
jsonArray.verify([{ name: 'Amir' }]); // [{ name: 'Amir' }]
// π
jsonArray.verify({}); // throws
jsonArray.verify({ name: 'Alice' }); // throws
jsonArray.verify('hello'); // throws
jsonArray.verify(null); // throws
Unions
# either<A, B, β¦>(Decoder<A>, Decoder<B>, β¦): Decoder<A | B | β¦> (source)
Accepts values accepted by any of the given decoders.
The decoders are tried on the input one by one, in the given order. The first one that accepts the input βwinsβ. If all decoders reject the input, the input gets rejected.
const decoder = either(number, string);
// π
decoder.verify('hello world') === 'hello world';
decoder.verify(123) === 123;
// π
decoder.verify(false); // throws
# oneOf<T>(values: T[]): Decoder<T> (source)
Accepts any value that is strictly-equal (using ===
) to one of the specified values.
const decoder = oneOf(['foo', 'bar', 3]);
// π
decoder.verify('foo') === 'foo';
decoder.verify(3) === 3;
// π
decoder.verify('hello'); // throws
decoder.verify(4); // throws
decoder.verify(false); // throws
For example, given an array of strings, like so:
oneOf(['foo', 'bar']);
# enum_(enum: MyEnum): Decoder<MyEnum> (source)
Accepts and return an enum value.
It works with numeric enums:
enum Fruit {
Apple,
Banana,
Cherry
}
const decoder = enum_(Fruit);
// π
decoder.verify(Fruit.Apple) === Fruit.Apple;
decoder.verify(Fruit.Banana) === Fruit.Banana;
decoder.verify(Fruit.Cherry) === Fruit.Cherry;
decoder.verify(0) === Fruit.Apple;
decoder.verify(1) === Fruit.Banana;
decoder.verify(2) === Fruit.Cherry;
// π
decoder.verify('Apple'); // throws
decoder.verify(-1); // throws
decoder.verify(3); // throws
As well as with string enums:
enum Fruit {
Apple = 'a',
Banana = 'b',
Cherry = 'c'
}
const decoder = enum_(Fruit);
// π
decoder.verify(Fruit.Apple) === Fruit.Apple;
decoder.verify(Fruit.Banana) === Fruit.Banana;
decoder.verify(Fruit.Cherry) === Fruit.Cherry;
decoder.verify('a') === Fruit.Apple;
decoder.verify('b') === Fruit.Banana;
decoder.verify('c') === Fruit.Cherry;
// π
decoder.verify('Apple'); // throws
decoder.verify(0); // throws
decoder.verify(1); // throws
decoder.verify(2); // throws
decoder.verify(3); // throws
# taggedUnion<A, B, β¦>(field: string, mapping: { value1: Decoder<A>, value2: Decoder<B>, β¦ }): Decoder<A | B | β¦> (source)
If you are decoding tagged unions you may want to use the taggedUnion()
decoder instead of the general purpose either()
decoder to get better error messages and better performance.
This decoder is optimized for tagged unions, i.e. a union of objects where one field is used as the discriminator.
const A = object({ tag: constant('A'), foo: string });
const B = object({ tag: constant('B'), bar: number });
const AorB = taggedUnion('tag', { A, B });
// ^^^
Decoding now works in two steps:
- Look at the
'tag'
field in the incoming object (this is the field that decides which decoder will be used) - If the value is
'A'
, then decoderA
will be used. If itβs'B'
, then decoderB
will be used. Otherwise, this will fail.
This is effectively equivalent to either(A, B)
, but will provide better error messages and is more performant at runtime because it doesnβt have to try all decoders one by one.
# select<T, A, B, β¦>(scout: Decoder<T>, selectFn: (result: T) => Decoder<A> | Decoder<B> | β¦): Decoder<A | B | β¦> (source)
Briefly peek at a runtime input using a βscoutβ decoder first, then decide which decoder to run on the (original) input, based on the information that the βscoutβ extracted.
It serves a similar purpose as taggedUnion()
, but is a generalization that works even if there isnβt a single discriminator, or the discriminator isnβt a string.
const decoder = select(
// First, validate/extract the minimal information to make a decision
object({ version: optional(number) }),
// Then select which decoder to run
(obj) => {
switch (obj.version) {
case undefined: return v1Decoder; // Suppose v1 doesn't have a discriminating field
case 2: return v2Decoder;
case 3: return v3Decoder;
default: return never('Invalid version');
}
},
);
// Decoder<V1 | V2 | V3>
Utilities
# define<T>(fn: (blob: unknown, ok, err) => DecodeResult<T>): Decoder<T> (source)
Defines a new Decoder<T>
, by implementing a custom acceptance function. The function receives three arguments:
blob
- the raw/unknown input (aka your external data)ok
- Callok(value)
to accept the input and returnvalue
err
- Callerr(message)
to reject the input with errormessage
The expected return value should be a DecodeResult<T>
, which can be obtained by returning the result of calling the provided ok
or err
helper functions. Please note that ok()
and err()
donβt perform side effects! Youβll need to return those values.
NOTE: This is the lowest-level API to define a new decoder, and therefore not recommended unless you have a very good reason for it. Most cases can be covered more elegantly by starting from an existing decoder and using
.transform()
or.refine()
on them instead.
// NOTE: Please do NOT implement an uppercase decoder like this! π
const uppercase: Decoder<string> = define(
(blob, ok, err) => {
if (typeof blob === 'string') {
// Accept the input
return ok(blob.toUpperCase());
} else {
// Reject the input
return err('I only accept strings as input');
}
}
);
// π
uppercase.verify('hi there') === 'HI THERE';
// π
uppercase.verify(123); // throws: 123
// ^^^ I only accept strings as input
The above example is just an example to illustrate how define()
works. It would be more idiomatic to implement an uppercase decoder as follows:
const uppercase: Decoder<string> = string.transform(s => s.toUpperCase());
# prep<T>(mapperFn: (raw: mixed) => mixed, decoder: Decoder<T>): Decoder<T> (source)
Pre-process the data input before passing it into the decoder. This gives you the ability to arbitrarily customize the input on the fly before passing it to the decoder. Of course, the input value at that point is still of unknown
type, so you will have to deal with that accordingly.
const decoder = prep(
// Will convert any input to an int first, before feeding it to
// positiveInteger. This will effectively also allow numeric strings
// to be accepted (and returned) as integers. If this ever throws,
// then the error message will be what gets annotated on the input.
x => parseInt(x),
positiveInteger,
);
// π
decoder.verify(42) === 42;
decoder.verify('3') === 3;
// π
decoder.verify('-3'); // throws: not a positive number
decoder.verify('hi'); // throws: not a number
# never: Decoder<never> (source)
# fail: Decoder<never> (source)
Rejects all inputs, and always fails with the given error message. May be useful for explicitly disallowing keys, or for testing purposes.
const decoder = object({
a: string,
b: optional(never('Key b has been removed')),
});
// π
decoder.verify({ a: 'foo' }); // { a: 'foo' };
decoder.verify({ a: 'foo', c: 'bar' }); // { a: 'foo' };
// π
decoder.verify({ a: 'foo', b: 'bar' }); // throws
# instanceOf<T>(klass: Klass<T>): Decoder<T> (source)
Accepts any value that is an instanceof
the given class.
const decoder = instanceOf(Error);
// π
const value = new Error('foo');
decoder.verify(value) === value;
// π
decoder.verify('foo'); // throws
decoder.verify(3); // throws
# lazy<T>(decoderFn: () => Decoder<T>): Decoder<T> (source)
Lazily evaluate the given decoder. This is useful to build self-referential types for recursive data structures.
type Tree = {
value: string;
children: Array<Tree>;
// ^^^^
// Self-reference defining a recursive type
};
const treeDecoder: Decoder<Tree> = object({
value: string,
children: array(lazy(() => treeDecoder)),
// ^^^^^^^^^^^^^^^^^^^^^^^
// Use lazy() like this to refer to the treeDecoder which is
// getting defined here
});