Package Exports
- decoders
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (decoders) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Elm-like decoders for use with Flow in JS.
See http://elmplayground.com/decoding-json-in-elm-1 for an introduction.
Why?
If you're using Flow to statically typecheck your JavaScript, you'll know that any JSON data coming from the outside is essentially free-form and untyped. In order to validate and enforce the correct shape of that data, you'll need "decoders".
For example, imagine your app expects a list of points in a JSON response:
{
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
# string(): Decoder<string> <>
Returns a decoder capable of decoding string values.
const mydecoder = guard(string);
mydecoder('hello world') === 'hello world'
mydecoder(123) // DecodeError
# 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
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
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>>
.
# 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]> <>
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 field 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" }],
])
# 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.
const mydecoder = guard(either(number, string));
mydecoder('hello world') === 'hello world';
mydecoder(123) === 123;
mydecoder(false) // DecodeError