Library for turning arbitrary input into a typed data.
MIT License
Library was implemented after Elm JSON.Decode, it mostly keeps same API although some parts are modifed to fit better JS and Flow semantics.
Library for turning arbitrary input into typed data. You can use this library to convert arbitrary JSON into nicely structured (and flow typed) data.
The core concept in this library is a decoder. It decodes JSON values into typed values. Library provides some primitive decoders for handling primitive data types and some functions to put those together to form decoders that can handle more complex data.
Library can also be used to put together structure data parsers that take strings of input and return typed data. See parse
function for more details.
Rest of the the document & provided code examples assumes that library is installed & imported as follows:
import * as Decoder from "./"
Decoder.Decoder<a>
A value that knows how to decode arbirtary JSON value (and/or parse arbitrary JSON string) into value of type a
.
Decoder.String:Decoder.Decoder<string>
Decoder that decodes JSON string to a string
value.
Decoder.decode(Decoder.String, true) //> Result.Error Expecting a String but instead got: `true`
Decoder.decode(Decoder.String, 42) //> Result.Error Expecting a String but instead got: `42`
Decoder.decode(Decoder.String, "Hello") //> Result.Ok "Hello"
Decoder.decode(Decoder.String, {hello:42}) //> Result.Error Expecting a String but instead got: {hello:42}`
Decoder.Boolean:Decoder.Decoder<boolean>
Decoder that decodes JSON boolean into a boolean
value.
Decoder.decode(Decoder.Boolean, true) //> Result.Ok true
Decoder.decode(Decoder.Boolean, 42) //> Result.Error Expecting a Boolean but instead got: `42`
Decoder.decode(Decoder.Boolean, 3.14) //> Result.Error Expecting a Boolean but instead got: `3.14`
Decoder.decode(Decoder.Boolean, "Hello") //> Result.Error Expecting a Boolean but instead got: `"Hello"`
Decoder.decode(Decoder.Boolean, {hello:42}) //> Result.Error Expecting a Boolean but instead got: `{hello:42}`
Decoder.Float:Decoder.Decoder<Decoder.float>
Decoder that decodes JSON number into a Decoder.float
value, which is an opaque type alias for number
type. Note that while (-
)Infinity
and NaN
are valid float
type values they aren't valid JSON numbers & this decoder will error decoding them. If you find yourself needing a way to decode as NaN
or Infinity
think twice and if you're absolutely sure let us know in an issue & given a convincing use case we'll add decoders for them.
Decoder.decode(Decoder.Float, 42) //> Result.Ok 42
Decoder.decode(Decoder.Float, 3.14) // Result.Ok 3.14
Decoder.decode(Decoder.Float, NaN) //> Result.Error Expecting a Float but instead got: `NaN`
Decoder.decode(Decoder.Float, Infinity) //> Result.Error Expecting a Float but instead got: `Infinity`
Decoder.decode(Decoder.Float, true) //> Result.Error Expecting a Float but instead got: `true`
Decoder.decode(Decoder.Float, "hello") // Result.Error Expecting a Float but instead got: `"hello"`
Decoder.decode(Decoder.Float, {hello:42}) //> Result.Error Expecting a Float but instead got: `{hello:42}`
Decoder.Integer:Decoder.Decoder<Decoder.integer>
Decoder that decodes JSON number into a Decoder.integer
value, which is an opaque type alias for number
type guaranteed to be an integer (passing it to Number.isInteger
returns true
) and there for it excludes (-
)Infinity
and NaN
:
Decoder.decode(Decoder.Integer, 42) //> Ok 42
Decoder.decode(Decoder.Integer, 3.14) // Result.Error Expecting an Integer but instead got: `3.14`
Decoder.decode(Decoder.Integer, NaN) //> Result.Error Expecting an Integer but instead got: `NaN`
Decoder.decode(Decoder.Integer, Infinity) //> Result.Error Expecting an Integer but instead got: `Infinity`
Decoder.decode(Decoder.Integer, true) //> Result.Error Expecting an Integer but instead got: `true`
Decoder.decode(Decoder.Integer, "hello") //> Result.Error Expecting an Integer but instead got: `"hello"`
Decoder.decode(Decoder.Integer, {hello:42}) //> Result.Error Expecting an Integer but instead got: `{hello:42}`
Decoder.optional <a> (Decoder<a>):Decoder.Decoder<?a>)
Creates decoder that decodes optional (null
/ undefined
) JSON values into an optional value.
Decoder.decode(Decoder.optional(Decoder.Integer), 13) //> Result.Ok 13
Decoder.decode(Decoder.optional(Decoder.Integer), null) //> Result.Ok null
Decoder.decode(Decoder.optional(Decoder.Integer), undefined) //> Result.Ok null
Decoder.decode(Decoder.optional(Decoder.Integer), false) //> Result.Error Expecting an Integer but instead got: `false`
Decoder.decode(Decoder.optional(Decoder.Integer), "hello") //> Result.Error Expecting an Integer but instead got: `"hello"`
Decoder.array <a> (Decoder<a>):Decoder.Decoder<a[]>
Creates a decoder for JSON arrays, where each element is decoded via provided decoder:
Decoder.decode(Decoder.array(Decoder.Boolean), [true, false]) //> Result.Ok [true, false]
Decoder.decode(Decoder.array(Decoder.Float), [1, 2.2, 3]) //> Result.Ok [1, 2.2, 3]
Decoder.decode(Decoder.array(Decoder.Integer), [1, 2.2, 3]) //> Result.Error Expecting an Integer at input[1] but instead got: `2.2`
Decoder.dictionary <a> (Decoder<a>):Decoder.Docoder<{[string]:a}>
Creates a decoder for JSON (dictionary) objects, where each value is decoded via provided decoder. If you are trying to decode JSON objects that have values of different types (a.k.a structs) consider using Decoder.record
instead.
Decoder.decode(Decoder.dictionary(Decoder.Float), {
alice: 42,
bob: 99.8
}) //> Result.Ok {"alice":42,"bob":99.8}
Decoder.decode(Decoder.dictionary(Decoder.Integer), {
alice: 42,
bob: 99.8
}) //> Result.Error Expecting an Integer at input["bob"] but instead got: `99.8`
Decoder.record <a:{}> (a):Decoder.Decoder<Decoder.Record<a>>
Creates a decoder for JSON (struct) objects, where each field is decoded with corresponding decoder over corresponding field in JSON (struct):
const point = Decoder.record({
x: Decoder.Integer,
y: Decoder.Integer
})
Decoder.decode(point, { x: 3, y: 5 }) //> Result.Ok {x:3, y:5}
Decoder.decode(point, { x: 3, y: 5, z: 7 }) //> Result.Ok {x:3, y:5}
Decoder.decode(point, { x: 3, y: 5.2 }) //> Result.Error Expecting an Integer at input["y"] but instead got: `5.2`
Decoder.field <a> (string, Decoder.Decoder<a>):Decoder.Decoder<a>
Creates a decoder JSON object property decoder, where property matching a provided name is decoded via provided decoder:
Decoder.decode(Decoder.field("x", Decoder.Integer), { x: 3 }) //> Result.Ok 3
Decoder.decode(Decoder.field("x", Decoder.Integer), { x: 3, y: 4 }) //> Result.Ok 3
Decoder.decode(Decoder.field("x", Decoder.Integer), { x: true }) //> Result.Error Expecting an Integer at input["x"] but instead got: `true`
Decoder.decode(Decoder.field("x", Decoder.Integer), { y: 4 }) //> Result.Error Expecting an object with a field named 'x' but instead got: `{"y":4}`
Decoder.decode(Decoder.field("x", Decoder.Integer), "x=3") //> Result.Error Expecting an object with a field named 'x' but instead got: `"x=3"`
Decoder.decode(Decoder.field("name", Decoder.String), { name: "Tom" }) //> Result.Ok "Tom"
Note that object can have other fields. Lots of them! The only thing this decoder cares about is if x
is present and that the value there is an Integer
.
Decoder.at <a> (string[], Decoder.Decoder<a>):Decoder.Decoder<a>
Creates a decode for a nested JSON object property:
const profile = { person: { name: "Tom", age: 42 } }
Decoder.decode(Decoder.at(["person", "name"], Decoder.String), profile) //> Result.Ok "Tom"
Decoder.decode(Decoder.at(["person", "age"], Decoder.Integer), profile) //> Result.Ok 42
This is really just a shorthand for saying things like:
Decoder.decode(
Decoder.field("person", Decoder.field("age", Decoder.Integer)),
profile
) //> Result.Ok 42
Decoder.index <a> (number, Decoder.Decoder<a>):Decoder.Decoder<a>
Creates a decoder JSON array element decoder, where provided number is an index for the element which is decoded via provided decoder:
const users = ["alice", "bob", "chuck"]
Decoder.decode(Decoder.index(0, Decoder.String), users) //> Result.Ok "alice"
Decoder.decode(Decoder.index(1, Decoder.String), users) //> Result.Ok "bob"
Decoder.decode(Decoder.index(2, Decoder.String), users) //> Result.Ok "chuck"
Decoder.decode(Decoder.index(3, Decoder.String), users) //> Result.Error Expecting a longer (>=4) array but instead got: `["alice","bob","chuck"]`
Decoder.maybe <a> (Decoder.Decoder<a>):Decoder.Decoder<?a>
Creates decoder helpful for dealing with optional fields:
const tom = { name: "tom", age: 42 }
Decoder.decode(Decoder.maybe(Decoder.field("age", Decoder.Integer)), tom) //> Result.Ok 42
Decoder.decode(Decoder.maybe(Decoder.field("name", Decoder.Integer)), tom) //> Result.Ok null
Decoder.decode(Decoder.maybe(Decoder.field("height", Decoder.Float)), tom) //> Result.Ok null
Decoder.decode(Decoder.field("age", Decoder.maybe(Decoder.Integer)), tom) //> Result.Ok 42
Decoder.decode(Decoder.field("name", Decoder.maybe(Decoder.Integer)), tom) //> Result.Ok null
Decoder.decode(Decoder.field("height", Decoder.maybe(Decoder.Integer)), tom) //> Result.Error Expecting an object with a field named 'height' but instead got: `{"name":"tom","age":42}`
Notice the last example! Error says that object with a field named height
is expected but passed object does not has one so it errors.
Point is, maybe
will make exactly what it contains conditional. For optional fields, this means you probably want it outside a use of field
or at
.
Decoder.annul <a> (a):Decoder.Decoder<a>
Creates a decoder that decodes null
as provided value. Decoding anything but
null
will error.
Decoder.decode(Decoder.annul(false), null) //> Result.Ok false
Decoder.decode(Decoder.annul(42), null) //> Result.Ok 42
Decoder.decode(Decoder.annul(42), 42) //> Result.Error Expecting a null but instead got: `42`
Decoder.decode(Decoder.annul(42), false) //> Result.Error Expecting a null but instead got: `false`
Decoder.either <a> (Decoder.Decoder<a>[]):Decoder<a>
Creates a decoder that tries provided decoders until one succeeds or all of them error. It is useful if the JSON may come in a couple of different formats. For example, say you want to read an array of strings, but some of the elements can be null
s.
const badName = Decoder.either(Decoder.String, Decoder.annul(""))
Decoder.decode(Decoder.array(badName), ["alice", "bob", null, "chuck"]) //> ["alice", "bob", "", "chuck"]
Why would someone generate JSON like this? Questions like this are not good for your health. The point is that you can use either
to handle situations like this!
You could also use either
to help version your data. Try the latest format, then a few older ones that you still support.
Decoder.ok <a> (a):Decoder.Decoder<a>
Creates a decoder that decodes to provided value
regardless of what is it decoding.
Decoder.decode(Decoder.ok(42), true) //> Result.Ok 42
Decoder.decode(Decoder.ok(42), [1, 2, 3]) //> Result.Ok 42
Decoder.decode(Decoder.ok(42), "hello") //> Result.Ok 42
It is mostly useful in combination with either
:
const name = Decoder.either(Decoder.field('username', Decoder.String),
Decoder.field('email', Decoder.String),
Decoder.ok('stranger'))
Decoder.decode(name, {username:"Jack"}) //> Result.Ok "Jack"
Decoder.decode(name, {email:"[email protected]"}) //> Result.Ok "[email protected]"
Decoder.decode(name, {}) //> Result.Ok "stranger"
Decoder.error <a> (string):Decoder.Decoder<a>
Creates a decoder that errors with provided message regardless of what is it decoding.
Decoder.decode(Decoder.error("Boom!"), true) //> Result.Error Boom!
Decoder.decode(Decoder.error("Boom!"), [1, 2, 3]) //> Result.Error Boom!
Decoder.decode(Decoder.error("Boom!"), "hello") //> Result.Error Boom!
It is useful in combination with either
to provide more contectual error messages:
const phone = Decoder.either(
Decoder.field('cell', Decoder.String),
Decoder.field('home', Decoder.String),
Decoder.error('No phone number'))
Decoder.decode(phone, {cell:"415-5588-0000", home:"415-8855-0000"}) //> Result.Ok "415-5588-0000"
Decoder.decode(phone, {home:"415-8855-0000"}) //> Result.Ok "415-8855-0000"
Decoder.decode(phone, {}) //> Result.Error No phone number
Decoder.decode <a> (Decoder.Decoder<a>, json:mixed):Decoder.Result<a>
Runs given Decoder<a>
on a given JSON value. Returns Result
that either
contains Decoder.Error
if value can't be decoded with a given decoder or
a Result.Ok<a>
.
Decoder.parse <a> (Decoder.Decoder<a>, input:string):Decoder.Result<a>
Parses given input
string into a JSON value and then runs given Decoder<a>
on it. Returns Result
with Result.Error<Decoder.ParseError>
if the string
is not well-formed JSON or Result.Error<Decoder.Error>
if the value can't be
decoded with a given Decoder<a>
. If operation is successfull returns
Result.Ok<a>
.
Decoder.parse(Decoder.Boolean, "true") //> Result.Ok true
Decoder.parse(Decoder.Boolean, "42") //> Result.Error Expecting a Boolean but instead got: `42`
Decoder.parse(Decoder.Boolean, "{") //> Result.Error Parse error: Unexpected end of JSON input
Decoder.parse(Decoder.field("a", Decoder.Integer), '{ "a": 42 }') //> Result.Ok 42
Library can also be used to extract typed data from arbitrary JS objects and all of the decoders covered will work. There some additional decoders that are specific to data extraction from non-JSON values.
Decoder.accessor <a> (string, Decoder.Decoder<a>):Decoder.Decoder<a>
Creates a decoder that decodes return value of the method that has name as passed string and on an object being decoded:
Decoder.decode(Decoder.accessor("cwd", Decoder.String), process) //> Result.Ok "/Users/gozala/Projects/decoder.flow"
Decoder.decode(Decoder.accessor("pwd", Decoder.String), process) //> Result.Error Expecting an object with a method named 'pwd' but instead got: `{/*...*/}`
Decoder.form <a:{}> (a):Decoder.Decoder<Decoder.Record<a>>
Creates a decoder for objects, where each field is decoded with a corresponding decoder over the passed object. Note that unlike Decoder.record
each field is decoded from the object
itself rather than same named field, there for fields of the result can be formed arbitrarily:
Decoder.decode(
Decoder.form({
title: Decoder.field("title", Decoder.String),
cwd: Decoder.accessor("cwd", Decoder.String),
architecture: Decoder.at(
["config", "variables", "host_arch"],
Decoder.String
),
heapUsed: Decoder.accessor(
"memoryUsage",
Decoder.field("heapUsed", Decoder.Integer)
)
}),
process
) //> Result.Ok Result.Ok {"title":"/usr/local/bin/node","cwd":"/Users/gozala/Projects/decoder.flow","architecture":"x64","heapUsed":52124520}
npm install decoder.flow