decoders

Decoder<T>

The Decoder class represents a type-safe decoder that validates and transforms untrusted data

The Decoder<T> class is the core abstraction in the decoders library. It represents a reusable, composable decoder that can validate and transform untrusted input data into typed values.

Decoding Methods

.verify()

Method .verify( blob: unknown ): T (source)
Method .verify( blob: unknown, formatter: Formatter ): T

Validates an input value. Returns the decoded value or throws.

Try it
number.verify(input)
InputResult
123
3.1415
"hello" ^^^^^^^ Must be number

.value()

Method .value( blob: unknown ): T | undefined (source)

Validates an input value. Returns the decoded value or return undefined. Never throws.

Try it
number.value(input)
InputResult
3
undefined
42
undefined

.decode()

Method .decode( blob: unknown ): DecodeResult<T> (source)

Validates an input value. Returns a DecodeResult type. Never throws.

type DecodeResult<T> =
  | { ok: true; value: T }
  | { ok: false; error: Annotation };

In case of error, an Annotation will be returned, which is a structured "copy" of the input value with annotations inlined on the rejected parts. Typically you will want to call formatInline or formatShort on this. See Error Formatting.

number.decode(3); // { ok: true, value: 3 }
number.decode('hi'); // { ok: false, error: ... }

Best practices

MethodReturnsOn failureWhen to use?
.verify()TThrowsYou want to just fail fast.
.value()TReturns undefinedYou have sensible defaults.
.decode()DecodeResult<T>Returns full detailsYou need fine-grained error handling.

Transformation Methods

.transform()

Method .transform( transformFn: (T) => V ): Decoder<V> (source)

Builds a new decoder that transforms the decoded value using a function.

Try it
string  .transform(s => s.toUpperCase())  .verify(input)
InputResult
"FOO"
"HELLO, WORLD!"
4 ^ Must be string

.refine()

Method .refine( predicate: T => boolean, message: string ): Decoder<T> (source)

Adds an extra condition. Rejects values that don't match the predicate.

Try it
number  .refine(n => n % 2 !== 0, 'Must be odd')  .verify(input)
InputResult
3
42 ^^ Must be odd
"hi" ^^^^ Must be number

.refineType()

Method .refineType(  ): Decoder<SubT> (source)

Available since 2.7.

Narrows the return type to a subtype. Useful for branded types. No runtime effect.

const user = object({ name: string }).refineType<Person>();

.reject()

Method .reject( rejectFn: T => string | null ): Decoder<T> (source)

Like .refine(), but lets you return a dynamic error message. Return null to accept, or a string to reject.

Try it
string  .reject(s => s.length > 5 ? `Too long (${s.length} chars)` : null)  .verify(input)
InputResult
"hello"
"this string is too long" ^^^^^^^^^^^^^^^^^^^^^^^^^ Too long (23 chars)
42 ^^ Must be string

.describe()

Method .describe( message: string ): Decoder<T> (source)

Overrides the error message when the decoder rejects.

Try it
oneOf(['a', 'e', 'i', 'o', 'u'])  .describe('Must be a vowel')  .verify(input)
InputResult
"a"
"x" ^^^ Must be a vowel
42 ^^ Must be a vowel

Composition Methods

.then()

.then() was renamed to .chain() in 2.9 to avoid being mistaken for a Promise.


.chain()

Method .chain( next: (blob: T, ok, err) => DecodeResult<V> | Decoder<V> ): Decoder<V>
Method .chain( next: Decoder<V> ): Decoder<V> (source)

Low-level method to send decoded output into another decoder or acceptance function. Prefer .transform(), .refine(), or .pipe() in most cases.


.pipe()

Method .pipe( next: Decoder<V> ): Decoder<V> (source)
Method .pipe( next: (blob: T) => Decoder<V> ): Decoder<V>

Available since 2.4.

Sends the decoded output into another decoder for further validation.

Try it
string  .transform(s => s.split(',').map(Number))  .pipe(array(positiveInteger))  .verify(input)
InputResult
[7]
[1, 2, 3]
[ 1, -3, ^^ Number must be positive (at index 1) ]
[ NaN, ^^^ Number must be finite (at index 0) ]
[ 3.14, ^^^^ Number must be an integer (at index 0) ]
123 ^^^ Must be string
true ^^^^ Must be string
null ^^^^ Must be string

Dynamic decoder selection

You can also use .pipe() to dynamically select a decoder based on the input value.

Try it
const point2d = tuple(number, number);const point3d = tuple(number, number, number);string  .transform(s => s.split(',').map(Number))  .pipe(tup => tup.length === 2 ? point2d : tup.length === 3 ? point3d : never('Must be 2D or 3D point'))  .verify(input)
InputResult
[ 1, ] ^ Must be 2D or 3D point
[1, 2]
[1, 2, 3]
[ 1, 2, 3, 4, ] ^ Must be 2D or 3D point

Inferring the type of a Decoder

Extracts the output type from a Decoder. Other libraries call this "infer".

import type { DecoderType } from 'decoders';
import { array, number, object, string } from 'decoders';

const tagsDecoder = array(string);

const personDecoder = object({
  name: string,
  age: number,
});

type Tags = DecoderType<typeof tagsDecoder>;
// string[]

type Person = DecoderType<typeof personDecoder>;
// { name: string; age: number }

On this page