Built-in decoders

All β€œbatteries included” decoders available in the standard library.


Strings


# 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

# 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

# url: Decoder<URL> (source)

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<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_: 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 = set(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:

  1. Look at the 'tag' field in the incoming object (this is the field that decides which decoder will be used)
  2. If the value is 'A', then decoder A will be used. If it’s 'B', then decoder B 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:

  1. blob - the raw/unknown input (aka your external data)
  2. ok - Call ok(value) to accept the input and return value
  3. err - Call err(message) to reject the input with error message

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
});