All Projects → nvie → Decoders

nvie / Decoders

Licence: mit
Elegant validation library for type-safe input data (for TypeScript and Flow)

Programming Languages

javascript
184084 projects - #8 most used programming language
typescript
32286 projects
elm
856 projects
flow
126 projects
type
21 projects

Projects that are alternatives of or similar to Decoders

Vue Formulate
⚡️ The easiest way to build forms with Vue.
Stars: ✭ 1,947 (+691.46%)
Mutual labels:  validation, input
python-valid8
Yet another validation lib ;). Provides tools for general-purpose variable validation, function inputs/outputs validation as well as class fields validation. All entry points raise consistent ValidationError including all contextual details, with dynamic inheritance of ValueError/TypeError as appropriate.
Stars: ✭ 24 (-90.24%)
Mutual labels:  validation, input
intl-tel-input-rails
intl-tel-input for the Rails asset pipeline
Stars: ✭ 35 (-85.77%)
Mutual labels:  validation, input
Revalidate
Elegant and composable validations
Stars: ✭ 363 (+47.56%)
Mutual labels:  validation, composition
Hyperform
Capture form validation back from the browser
Stars: ✭ 729 (+196.34%)
Mutual labels:  validation, input
Intl Tel Input
A JavaScript plugin for entering and validating international telephone numbers
Stars: ✭ 5,963 (+2323.98%)
Mutual labels:  validation, input
Validator.js
String validation
Stars: ✭ 18,842 (+7559.35%)
Mutual labels:  validation, input
Awesomevalidation
Android validation library which helps developer boil down the tedious work to three easy steps.
Stars: ✭ 1,093 (+344.31%)
Mutual labels:  validation, input
Validation
Framework agnostic validation library for PHP
Stars: ✭ 146 (-40.65%)
Mutual labels:  validation, input
Netdevpack
A smart set of common classes and implementations to improve your development productivity.
Stars: ✭ 220 (-10.57%)
Mutual labels:  validation
Gwion
🎵 strongly-timed musical programming language
Stars: ✭ 235 (-4.47%)
Mutual labels:  composition
Nstack
Type-safe, composable microservices for data analytics
Stars: ✭ 219 (-10.98%)
Mutual labels:  composition
Spring Dubbo Service
微服务 spring dubbo项目:dubbo rpc;druid数据源连接池;mybatis配置集成,多数据源;jmx监控MBean;定时任务;aop;ftp;测试;Metrics监控;参数验证;跨域处理;shiro权限控制;consul服务注册,发现;redis分布式锁;SPI服务机制;cat监控;netty服务代理;websocket;disconf;mongodb集成;rest;docker;fescar
Stars: ✭ 224 (-8.94%)
Mutual labels:  validation
Wickedengine
3D engine focusing on modern rendering techniques and performance.
Stars: ✭ 3,148 (+1179.67%)
Mutual labels:  input
Sheet Router
fast, modular client-side router
Stars: ✭ 219 (-10.98%)
Mutual labels:  composition
Angular Validation
AngularJS Form Validation made simple
Stars: ✭ 243 (-1.22%)
Mutual labels:  validation
Schematics
Project documentation: https://schematics.readthedocs.io/en/latest/
Stars: ✭ 2,461 (+900.41%)
Mutual labels:  validation
React Intl Tel Input
Rewrite International Telephone Input in React.js. (Looking for maintainers, and PRs & contributors are also welcomed!)
Stars: ✭ 212 (-13.82%)
Mutual labels:  input
Pygame Menu
Menu for pygame. Simple, lightweight and easy to use
Stars: ✭ 244 (-0.81%)
Mutual labels:  input
Validictory
🎓 deprecated general purpose python data validator
Stars: ✭ 242 (-1.63%)
Mutual labels:  validation

Decoders logo

npm Build Status Coverage Status Minified Size

Elegant and battle-tested validation library for type-safe input data for TypeScript and Flow. The API is inspired by Elm’s JSON decoders, hence the name.

See https://nvie.com/posts/introducing-decoders/ for an introduction.

Why?

If you're using Flow or TypeScript to statically typecheck your JavaScript, you'll know that any data coming from outside your program’s boundaries is essentially untyped and unsafe. "Decoders" can help to validate and enforce the correct shape of that data.

For example, imagine your app expects a list of points in an incoming HTTP request:

{
  points: [
    { x: 1, y: 2 },
    { x: 3, y: 4 },
  ],
}

In order to decode this, you'll have to tell Flow about the expected structure, and use the decoders to validate at runtime that the free-form data will be in the expected shape.

type Point = { x: number, y: number };

type Payload = {
    points: Array<Point>,
};

Here's a decoder that will work for this type:

import { array, guard, number, object } from 'decoders';

const point = object({
    x: number,
    y: number,
});

const payload = object({
    points: array(point),
});

const payloadGuard = guard(payload);

And then, you can use it to decode values:

>>> payloadGuard(1)      // throws!
>>> payloadGuard('foo')  // throws!
>>> payloadGuard({       // OK!
...     points: [
...         { x: 1, y: 2 },
...         { x: 3, y: 4 },
...     ],
... })

API

The decoders package consists of a few building blocks:

Primitives

# number: Decoder<number> <>

Returns a decoder capable of decoding finite (!) numbers (integer or float values). This means that values like NaN, or positive and negative Infinity are not considered valid numbers.

const mydecoder = guard(number);
mydecoder(123) === 123;
mydecoder(-3.14) === -3.14;
mydecoder(NaN); // DecodeError
mydecoder('not a number'); // DecodeError

# integer: Decoder<integer> <>

Like number, but only decodes values that are whole numbers.

const mydecoder = guard(integer);
mydecoder(123) === 123;
mydecoder(-3.14); // DecodeError: floats aren't valid integers
mydecoder(NaN); // DecodeError
mydecoder('not a integer'); // DecodeError

# string: Decoder<string> <>

Returns a decoder capable of decoding string values.

const mydecoder = guard(string);
mydecoder('hello world') === 'hello world';
mydecoder(123); // DecodeError

# nonEmptyString: Decoder<string> <>

Like string, but will fail on inputs with only whitespace (or the empty string).

const mydecoder = guard(nonEmptyString);
mydecoder('hello world') === 'hello world';
mydecoder(123); // DecodeError
mydecoder('  '); // DecodeError
mydecoder(''); // DecodeError

# regex(): Decoder<string> <>

Returns a decoder capable of decoding string values that match the given regular expression.

const mydecoder = guard(regex(/^[0-9]+$/));
mydecoder('12345') === '12345';
mydecoder('foo'); // DecodeError

# email: Decoder<string> <>

Returns a decoder capable of decoding email addresses (using a regular expression).

const mydecoder = guard(email);
mydecoder('foo'); // DecodeError
mydecoder('[email protected]') === '[email protected]';

# boolean: Decoder<boolean> <>

Returns a decoder capable of decoding boolean values.

const mydecoder = guard(boolean);
mydecoder(false) === false;
mydecoder(true) === true;
mydecoder(undefined); // DecodeError
mydecoder('hello world'); // DecodeError
mydecoder(123); // DecodeError

# truthy: Decoder<boolean> <>

Returns a decoder capable of decoding any input value to its "truthy value".

const mydecoder = guard(truthy);
mydecoder(false) === false;
mydecoder(true) === true;
mydecoder(undefined) === false;
mydecoder('hello world') === true;
mydecoder('false') === true;
mydecoder(0) === false;
mydecoder(1) === true;
mydecoder(null) === false;

# numericBoolean: Decoder<boolean> <>

Returns a decoder capable of decoding numbers to their boolean representation.

const mydecoder = guard(numericBoolean);
mydecoder(-1) === true;
mydecoder(0) === false;
mydecoder(123) === true;
mydecoder(false); // DecodeError
mydecoder(true); // DecodeError
mydecoder(undefined); // DecodeError
mydecoder('hello'); // DecodeError

# date: Decoder<Date> <>

Returns a decoder capable of decoding Date values.

const now = new Date();
const mydecoder = guard(date);
mydecoder(now) === now;
mydecoder(123); // DecodeError
mydecoder('hello'); // DecodeError

# iso8601: Decoder<Date> <>

Returns a decoder capable of decoding ISO8601-formatted date strings. This is very useful for working with dates in APIs: serialize them as .toISOString() when sending, decode them as iso8601 when receiving.

NOTE: This decoder reads a string, but returns a Date instance.

const mydecoder = guard(iso8601);
mydecoder('2020-06-01T12:00:00Z'); // new Date('2020-06-01T12:00:00Z')
mydecoder('2020-06-01'); // DecodeError
mydecoder('hello'); // DecodeError
mydecoder(123); // DecodeError

# null_: Decoder<null> <>

Returns a decoder capable of decoding the constant value null.

const mydecoder = guard(null_);
mydecoder(null) === null;
mydecoder(false); // DecodeError
mydecoder(undefined); // DecodeError
mydecoder('hello world'); // DecodeError

# undefined_: Decoder<void> <>

Returns a decoder capable of decoding the constant value undefined.

const mydecoder = guard(undefined_);
mydecoder(undefined) === undefined;
mydecoder(null); // DecodeError
mydecoder(false); // DecodeError
mydecoder('hello world'); // DecodeError

# constant<T>(value: T): Decoder<T> <>

Returns a decoder capable of decoding just the given constant value.

NOTE: In TypeScript, it is important to always use the following:

// ❌ Do NOT do this
constant('hello')           // will infer: Decoder<string>
constant(42)                // will infer: Decoder<number>

// ✅ Do this
constant('hello' as 'hello')  // will infer: Decoder<'hello'>
constant(42 as 42)            // will infer: Decoder<42>

For Flow, use this syntax:

constant(('something': 'something'))
constant((42: 42))

Example:

const mydecoder = guard(constant('hello' as 'hello'));
mydecoder('hello') === 'hello';
mydecoder('this breaks'); // DecodeError
mydecoder(false); // DecodeError
mydecoder(undefined); // DecodeError

# hardcoded<T>(value: T): Decoder<T> <>

Returns a decoder that will always return the provided value without looking at the input. This is useful to manually add extra fields.

const mydecoder = guard(hardcoded(2.1));
mydecoder('hello') === 2.1;
mydecoder(false) === 2.1;
mydecoder(undefined) === 2.1;

# fail(): Decoder<empty> <>

Returns a decoder that will always fail with the given error messages, no matter what the input. May be useful for explicitly disallowing keys, or for testing purposes.

const mydecoder = guard(object({ a: string, b: optional(fail('Key b has been removed')) })));
mydecoder({ a: 'foo' }) === { a: 'foo' }
mydecoder({ a: 'foo', c: 'bar' }) === { a: 'foo' }
mydecoder({ a: 'foo', b: 'bar' })  // DecodeError

# mixed: Decoder<mixed> <>
# unknown: Decoder<unknown> <>

Returns a decoder that will simply pass through any input value, never fails. This effectively returns a Decoder<mixed>, which is not that useful. Use sparingly.

Same as unknown in TypeScript.

const mydecoder = guard(mixed);
mydecoder('hello') === 'hello';
mydecoder(false) === false;
mydecoder(undefined) === undefined;
mydecoder([1, 2]) === [1, 2];

Compositions

Composite decoders are "higher order" decoders that can build new decoders from existing decoders that can already decode a "subtype". Examples are: if you already have a decoder for a Point (= Decoder<Point>), then you can use array() to automatically build a decoder for arrays of points: array(pointDecoder), which will be of type Decoder<Array<Point>>.

# optional<T>(Decoder<T>): Decoder<T | void> <>

Returns a decoder capable of decoding either a value of type T, or undefined, provided that you already have a decoder for T.

const mydecoder = guard(optional(string));
mydecoder('hello') === 'hello';
mydecoder(undefined) === undefined;
mydecoder(null); // DecodeError
mydecoder(0); // DecodeError
mydecoder(42); // DecodeError

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<T>): Decoder<T | null> <>

Returns a decoder capable of decoding either a value of type T, or null, provided that you already have a decoder for T.

const mydecoder = guard(nullable(string));
mydecoder('hello') === 'hello';
mydecoder(null) === null;
mydecoder(undefined); // DecodeError
mydecoder(0); // DecodeError
mydecoder(42); // DecodeError

# maybe<T>(Decoder<T>): Decoder<?T> <>

Returns a decoder capable of decoding either a value of type T, or null, or undefined, provided that you already have a decoder for T.

const mydecoder = guard(maybe(string));
mydecoder('hello') === 'hello';
mydecoder(null) === null;
mydecoder(undefined) === undefined;
mydecoder(0); // DecodeError
mydecoder(42); // DecodeError

# array<T>(Decoder<T>): Decoder<Array<T>> <>

Returns a decoder capable of decoding an array of T's, provided that you already have a decoder for T.

const mydecoder = guard(array(string));
mydecoder(['hello', 'world']) === ['hello', 'world'];
mydecoder(['hello', 1.2]); // DecodeError

# nonEmptyArray<T>(Decoder<T>): Decoder<Array<T>> <>

Like array(), but will fail on inputs with 0 elements.

const mydecoder = guard(nonEmptyArray(string));
mydecoder(['hello', 'world']) === ['hello', 'world'];
mydecoder(['hello', 1.2]); // DecodeError
mydecoder([]); // DecodeError

# tuple1<T1>(Decoder<T1>): Decoder<[T1]> <>
# tuple2<T1, T2>(Decoder<T1>, Decoder<T2>): Decoder<[T1, T2]> <>
# tuple3<T1, T2, T3>(Decoder<T1>, Decoder<T2>, Decoder<T3>): Decoder<[T1, T2, T3]> <>
# tuple4<T1, T2, T3, T4>(Decoder<T1>, Decoder<T2>, Decoder<T3>, Decoder<T4>): Decoder<[T1, T2, T3, T4]> <>
# tuple5<T1, T2, T3, T4, T5>(Decoder<T1>, Decoder<T2>, Decoder<T3>, Decoder<T3>, Decoder<T4>, Decoder<T5>): Decoder<[T1, T2, T3, T4, T5]> <>
# tuple6<T1, T2, T3, T4, T5, T6>(Decoder<T1>, Decoder<T2>, Decoder<T3>, Decoder<T4>, Decoder<T5>, Decoder<T6>): Decoder<[T1, T2, T3, T4, T5, T6]> <>

Returns a decoder capable of decoding a 2-tuple of (T1, T2)'s, provided that you already have a decoder for T1 and T2. A tuple is like an Array, but the number of items in the array is fixed (two) and their types don't have to be homogeneous.

const mydecoder = guard(tuple2(string, number));
mydecoder(['hello', 1.2]) === ['hello', 1.2];
mydecoder(['hello', 'world']); // DecodeError

# object<O: { [field: string]: Decoder<any> }>(mapping: O): Decoder<{ ... }> <>

Returns a decoder capable of decoding objects of the given shape corresponding decoders, provided that you already have decoders for all values in the mapping.

const mydecoder = guard(
    object({
        x: number,
        y: number,
    })
);
mydecoder({ x: 1, y: 2 }) === { x: 1, y: 2 };
mydecoder({ x: 1, y: 2, z: 3 }) === { x: 1, y: 2 }; // ⚠️
mydecoder({ x: 1 }); // DecodeError (Missing key: "y")

For more information, see also The difference between object, exact, and inexact.


# exact<O: { [field: string]: Decoder<any> }>(mapping: O): Decoder<{ ... }> <>

Like object(), but will fail if there are superfluous keys in the input data.

const mydecoder = guard(
    exact({
        x: number,
        y: number,
    })
);
mydecoder({ x: 1, y: 2 }) === { x: 1, y: 2 };
mydecoder({ x: 1, y: 2, z: 3 }); // DecodeError (Superfluous keys: "z")
mydecoder({ x: 1 }); // DecodeError (Missing key: "y")

For more information, see also The difference between object, exact, and inexact.


# inexact<O: { [field: string]: Decoder<any> }>(mapping: O): Decoder<{ ... }> <>

Like object(), but will retain any extra properties on the input type unvalidated that are not part of the decoder definition.

const mydecoder = guard(
    inexact({
        x: number,
    })
);

mydecoder({ x: 1, y: 2 }) === { x: 1, y: 2 };
mydecoder({ x: 1, y: 2, z: 3 }) === { x: 1, y: 2, z: 3 };
mydecoder({ x: 1 }); // DecodeError (Missing key: "y")

For more information, see also The difference between object, exact, and inexact.


# mapping<T>(Decoder<T>): Decoder<Map<string, T>> <>

Returns a decoder capable of decoding Map instances of strings-to-T's , provided that you already have a decoder for T.

The main difference between object() and mapping() is that you'd typically use object() if this is a record-like object, where you know all the field names and the values are heterogeneous. Whereas with Mappings the keys are typically unknown and the values homogeneous.

const mydecoder = guard(mapping(person)); // Assume you have a "person" decoder already
mydecoder({
    '1': { name: 'Alice' },
    '2': { name: 'Bob' },
    '3': { name: 'Charlie' },
}) ===
    Map([
        ['1', { name: 'Alice' }],
        ['2', { name: 'Bob' }],
        ['3', { name: 'Charlie' }],
    ]);

# dict<T>(Decoder<T>): Decoder<{ [string]: <T>}> <>

Like mapping(), but returns an object instead of a Map instance.

const mydecoder = guard(dict(person)); // Assume you have a "person" decoder already
mydecoder({
    '1': { name: 'Alice' },
    '2': { name: 'Bob' },
    '3': { name: 'Charlie' },
});

Would equal:

{
    "1": { name: "Alice" },
    "2": { name: "Bob" },
    "3": { name: "Charlie" },
}

# json: Decoder<JSONValue> <>

Returns a decoder capable of decoding any valid JSON value:

  • null
  • string
  • number
  • boolean
  • { [string]: JSONValue }
  • Array<JSONValue>
const mydecoder = guard(json);
mydecoder({
    name: 'Amir',
    age: 27,
    admin: true,
    image: null,
    tags: ['vip', 'staff'],
});

Any value returned by JSON.parse() should decode without failure.


# jsonObject: Decoder<JSONObject> <>

Like json, but will only decode when the JSON value is an object.

const mydecoder = guard(json);
mydecoder({}); // OK
mydecoder({ name: 'Amir' }); // OK

// Error: the following values are valid JSON values, but not Objects
mydecoder([]); // Error
mydecoder([{ name: 'Alice' }, { name: 'Bob' }]); // Error
mydecoder('hello'); // Error
mydecoder(null); // Error

# jsonArray: Decoder<JSONArray> <>

Like json, but will only decode when the JSON value is an array.

const mydecoder = guard(json);
mydecoder([]); // OK
mydecoder([{ name: 'Amir' }]); // OK

// Error: the following values are valid JSON values, but not Objects
mydecoder({}); // Error
mydecoder({ name: 'Alice' }); // Error
mydecoder('hello'); // Error
mydecoder(null); // Error

# either<T1, T2>(Decoder<T1>, Decoder<T2>): Decoder<T1 | T2>
<>
# either2<T1, T2>(Decoder<T1>, Decoder<T2>): Decoder<T1 | T2>
<>
# either3<T1, T2, T3>(Decoder<T1>, Decoder<T2>, Decoder<T3>): Decoder<T1 | T2 | T3> <>
...

Returns a decoder capable of decoding either one of T1 or T2, provided that you already have decoders for T1 and T2. Eithers exist for arities up until 9 (either, either3, either4, ..., either9).

const mydecoder = guard(either(number, string));
mydecoder('hello world') === 'hello world';
mydecoder(123) === 123;
mydecoder(false); // DecodeError

# dispatch<O: { [field: string]: (Decoder<T> | Decoder<V> | ...) }>(field: string, mapping: O): Decoder<T | V | ...> <>

Like the either family, but only for building unions of object types with a common field (like a type field) that lets you distinguish members.

The following two decoders are effectively equivalent:

type Rect = {| __type: 'rect', x: number, y: number, width: number, height: number |};
type Circle = {| __type: 'circle', cx: number, cy: number, r: number |};
//               ^^^^^^
//               Field that defines which decoder to pick
//                                               vvvvvv
const shape1: Decoder<Rect | Circle> = dispatch('__type', { rect, circle });
const shape2: Decoder<Rect | Circle> = either(rect, circle);

But using dispatch() will typically be more runtime-efficient than using either(). The reason is that dispatch() will first do minimal work to "look ahead" into the type field here, and based on that value, pick which decoder to invoke. Error messages will then also be tailored to the specific decoder.

The either() version will instead try each decoder in turn until it finds one that matches. If none of the alternatives match, it needs to report all errors, which is sometimes confusing.


# oneOf<T>(Array<T>): Decoder<T> <>

Returns a decoder capable of decoding values that are equal to any of the given constants. The returned value will always be one of the given constants at runtime, but the return type of this decoder will not be a union of constants, but a union of types, typically.

const mydecoder = guard(oneOf(['foo', 'bar', 3]));
mydecoder('foo') === 'foo';
mydecoder(3) === 3;
mydecoder('hello'); // DecodeError
mydecoder(4); // DecodeError
mydecoder(false); // DecodeError

For example, given an array of strings, like so:

oneOf(['foo', 'bar']);

The return type here will be Decoder<string>, not Decoder<('foo' | 'bar')>. (To obtain the latter, use either(constant('foo'), constant('bar')) instead.)


# instanceOf<T>(Class<T>): Decoder<T> <>

Returns a decoder capable of decoding values that are instances of the given class.

NOTE: Help wanted! The TypeScript annotation for this decoder needs help! If you know how to express it, please submit a PR. See https://github.com/nvie/decoders/blob/master/src/instanceOf.d.ts

const mydecoder = guard(instanceOf(Error));
const value = new Error('foo');
mydecoder(value) === value;
mydecoder('foo'); // DecodeError
mydecoder(3); // DecodeError

# map<T, V>(Decoder<T>, <T> => <V>): Decoder<V> <>

Given a decoder and a mapper function, will first decode the value using the given decoder, and on success, will call the mapper function on the decoded value. If the mapper function throws an error, the whole decoder will fail using the error message as the failure reason.

const upper = map(string, (s) => s.toUpperCase());

const mydecoder = guard(upper);
mydecoder(4); // DecodeError
mydecoder('foo') === 'FOO';

# compose<T, V>(Decoder<T>, Decoder<V, T>): Decoder<V> <>

Given a decoder for T and another one for V, will first decode using T, and then call the V decoder on the original value. This differs from map() in that it was access to the original value, but may assume the type value is already refined by the first decoder.

Although the compose() function is essentially more low-level and powerful then the map() function, it's mostly useful in combination with the predicate() helper function, which allows you to rely on an existing decoder, but add extra checks on the specific values that will be allowed at runtime.


# lazy<T>(() => Decoder<T>): Decoder<T> <>

Lazily evaluate the given decoder. This is useful to build self-referential types for recursive data structures. Example:

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

The difference between object, exact, and inexact

The three decoders in the "object" family of decoders only differ in how they treat extra properties on input values.

For example, for a definition like:

import { exact, inexact, number, object, string } from 'decoders';

const thing = {
    a: string,
    b: number,
};

And a runtime input of:

{
  a: "hi",
  b: 42,
  c: "extra",  // Note "c" is not a known field
}
Extra properties Output value Inferred type
object(thing) discarded {a: "hi", b: 42} {a: string, b: number}
exact(thing) not allowed ⚡️ Runtime error {a: string, b: number}
inexact(thing) retained {a: "hi", b: 42, c: "extra"} {a: string, b: number, [string]: mixed}

Building custom decoders

There are two main building blocks for defining your own custom decoders: map() and compose().

There are roughly 3 use cases that you will want to use:

  1. Transformation (i.e. read one type, but return another, or read a type but change its value before returning)
  2. Adding extra value requirements (i.e. decode using an existing decoder, but require an extra value check)
  3. Chaining multiple decoders (less common, more advanced)

Transformation

To read one type from the input, but return another, use:

const numericString: Decoder<number> = map(
    // At runtime, expect to read a string...
    string,
    // ...but return it as a number
    (s) => Number(s)
);

To read one type, but change its value before returning:

const upperCase: Decoder<string> = map(string, (s) => s.toUpperCase());

WARNING: While you can map anything to anything, it's typically NOT A GOOD IDEA to put too much transformation logic inside decoders. It's recommended to keep them minimal and only try to use them for the most basic use cases, like in the examples above. Keeping business logic outside decoders makes them more reusable and composable.

Adding predicates

The easiest way to decode using an existing decoder, but enforcing extra runtime checks on their values is by using the compose(..., predicate(...)) construction:

const odd = compose(
    integer,
    predicate((n) => n % 2 !== 0, 'Must be odd')
);
const shortString = compose(
    string,
    predicate((s) => s.length < 8, 'Must be less than 8 chars')
);
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].