Decoder<T>
methods
All decoders have the following methods.
# .verify(blob: mixed): T (source)
Verifies the untrusted/unknown input and either accepts or rejects it. When accepted, returns a value of type T
. Otherwise fail with a runtime error.
For example, take this simple number decoder.
// π
number.verify(123); // 123
number.verify(3.1415); // 3.1415
// π
number.verify('hello'); // throws
// Decoding error:
// "hello"
// ^^^^^^^ Must be number
# .value(blob: mixed): T | undefined (source)
Verifies the untrusted/unknown input and either accepts or rejects it. When accepted, returns the decoded T
value directly. Otherwise returns undefined
.
Use this when youβre not interested in programmatically handling the error message.
// π
number.value(3); // 3
string.value('hi'); // 'hi'
// π
number.value('hi'); // undefined
string.value(42); // undefined
NOTE: When you use this on
optional()
decoders, you cannot distinguish a rejected value from an acceptedundefined
input value.
# .decode(blob: mixed): DecodeResult<T> (source)
Verifies the untrusted/unknown input and either accepts or rejects it.
Contrasted with .verify()
, calls to .decode()
will never fail and instead return a result type.
For example, take this simple βnumberβ decoder. When given an number value, it will return an ok: true result. Otherwise, it will return an ok: false result with the original input value annotated.
// π
number.decode(3); // { ok: true, value: 3 };
// π
number.decode('hi'); // { ok: false, error: { type: 'scalar', value: 'hi', text: 'Must be number' } }
# .transform<V>(transformFn: (T) => V): Decoder<V> (source)
Accepts any value the given decoder accepts, and on success, will call the given function on the decoded result. If the transformation function throws an error, the whole decoder will fail using the error message as the failure reason.
const upper = string.transform((s) => s.toUpperCase());
// π
upper.verify('foo') === 'FOO'
// π
upper.verify(4); // throws
# .refine(predicate: T => boolean, message: string): Decoder<T> (source)
Adds an extra predicate to a decoder. The new decoder is like the original decoder, but only accepts values that also meet the predicate.
const odd = number.refine(
(n) => n % 2 !== 0,
'Must be odd'
);
// π
odd.verify(3) === 3;
// π
odd.verify(42); // throws: not an odd number
odd.verify('hi'); // throws: not a number
In TypeScript, if you provide a predicate that also is a type predicate, then this will be reflected in the return type, too.
# .reject(rejectFn: T => string | null): Decoder<T> (source)
Adds an extra predicate to a decoder. The new decoder is like the original decoder, but only accepts values that arenβt rejected by the given function.
The given function can return null
to accept the decoded value, or return a specific error message to reject.
Unlike .refine()
, you can use this function to return a dynamic error message.
const decoder = pojo
.reject(
obj => {
const badKeys = Object.keys(obj).filter(key => key.startsWith('_'));
return badKeys.length > 0
? `Disallowed keys: ${badKeys.join(', ')}`
: null;
}
);
// π
decoder.verify({ id: 123, name: 'Vincent' }) === { id: 123, name: 'Vincent' };
// π
decoder.verify({ id: 123, _name: 'Vincent' }) // throws: "Disallowed keys: _name"
# .describe(message: string): Decoder<T> (source)
Uses the given decoder, but will use an alternative error message in case it rejects. This can be used to simplify or shorten otherwise long or low-level/technical errors.
const vowel = oneOf(['a', 'e', 'i', 'o', 'u'])
.describe('Must be vowel');
# .then<V>(next: (blob: T, ok, err) => DecodeResult<V> | Decoder<V>): Decoder<V> (source)
# .then<V>(next: Decoder<V>): Decoder<V> (source)
Send the output of the current decoder into another decoder or acceptance function. The given acceptance function will receive the output of the current decoder as its input.
NOTE: This is an advanced, low-level, API. Itβs not recommended to reach for this construct unless there is no other way. Most cases can be covered more elegantly by
.transform()
,.refine()
, or.pipe()
instead.
# .pipe<V>(next: Decoder<V>): Decoder<V> (source)
# .pipe<V>(next: (blob: T) => Decoder<V>): Decoder<V> (source)
const decoder =
string
.transform((s) => s.split(',').map(Number))
.pipe(array(positiveInteger));
// π
decoder.verify('7') === [7];
decoder.verify('1,2,3') === [1, 2, 3];
// π
decoder.verify('1,-3') // -3 is not positive
decoder.verify('π'); // not a number
decoder.verify('3.14'); // not a whole number
decoder.verify(123); // not a string
decoder.verify(true); // not a string
decoder.verify(null); // not a string
Dynamic decoder selection with .pipe()
With .pipe()
you can also dynamically select another decoder, based on dynamic runtime value.
string
.transform((s) => s.split(',').map(Number))
.pipe((tup) =>
tup.length === 2
? point2d
: tup.length === 3
? point3d
: never('Invalid coordinate'),
);