Giter Club home page Giter Club logo

decoders's Introduction

Decoders logo

npm Build Status Coverage Status Minified Size

Type-safe data verification (inspired by Elm’s decoders, hence the name) for use with Flow or TypeScript.

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 { 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

# 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

# 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.

const mydecoder = guard(constant('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

# 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.

NOTE: 🙀 OMG, that type signature! Don't panic. Here's what it says with an example. Given this mapping of field-to-decoder instances:

{
  name: Decoder<string>,
  age: Decoder<number>,
}

compose a decoder of this type: Decoder<{ name: string, age: number }>.

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")

# 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")

# 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(mapping(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" },
}

# 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

decoders's People

Contributors

demarko avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar jfmengels avatar nvie avatar wanderley avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.