decoder(T) = fun((jsx:json_term()) -> result(T, errors()))
A decoder(T)
is an opaque datastructure that represents a composable
decoder. After composing a decoder for your JSON and final datastructure, you
can run it with decode/2
or decode/3
.
decoder(T)
an opaque datastructure. It is only exposed due to
dialyzer limitations.
error() = {unexpected_type, ExpectedType :: type(), jsx:json_term()} | {missing_field, field(), jsx:json_term()} | {missing_index, non_neg_integer(), jsx:json_term()} | {in_field, field(), [error(), ...]} | {at_index, non_neg_integer(), [error(), ...]} | {custom, any()} | {invalid_json, any()}
error()
is a (resursive) structure that gives detailed information about
what went wrong, and where.
Most primitive decoders may fail with an unexpected_type
error when the
JSON value is not the expected type. Those errors contain both the expected
type and the actual value.
When decoders like field/2
or index/2
are used,
missing_field
and missing_index
may be used when the provided field or
index was not found in the JSON data.
Errors that occur in the context of a field or index, for example when using
field/2
or list/1
, are nested in in_field
or at_index
which allows tracing the failure path.
Convenience and extended decoders using fail/1
will wrap their errors
in custom
to indicate a higher level failure.
invalid_json
error is
produced.
errors() = [error(), ...]
A nonempty list of error/0
s
field() = atom() | binary()
Depending on the options passed to decoder/3
, a field-name may be
either an atom()
, a binary()
or a mix of both. When using decoder/2
, field-names are always atoms. Note that those atoms must already
exist, or decoding will fail.
result(V, E) = {ok, V} | {error, E}
Running a decoder results in a result
, indicating either success (and
providing the decoded data) or failure, with a nonempty list of errors.
type() = binary | integer | pos_integer | neg_integer | non_neg_integer | null | boolean | float | map | list | nonempty_list
These may appear in unexpected_type
errors.
binary/0 | Decodes a JSON string as a binary() . |
integer/0 | Decodes a JSON integer as an integer() |
pos_integer/0 | Decodes a strictly positive JSON integer as a pos_integer() . |
neg_integer/0 | Decodes a negative JSON integer as a neg_integer() . |
non_neg_integer/0 | Decodes a positive JSON integer as a non_neg_integer() . |
float/0 | Decodes a JSON number as a float() |
null/0 | Equivalent to null(null). |
null/1 | Decodes null into an arbitrary value. |
boolean/0 | Decodes a JSON true or false to a boolean() |
value/0 | Extracts a raw jsx:json_term() |
atom/0 | Decodes a JSON value to an atom() |
atom/1 | Decodes a JSON value to one of a set of predefined atoms. |
existing_atom/0 | Decodes a JSON value to an existing atom. |
existing_atom/1 | Equivalent to atom(Allowed). |
email/0 | Decodes a JSON string as a binary() if and only if it looks like an
email address. |
full_date_tuple/1 | |
uuid/1 | Decode a UUIDv4 from a JSON string. |
integer/2 | Decode a bounded integer from JSON. |
nullable/1 | Decode a nullable value. |
nullable/2 | Decode a nullable value. |
field/2 | Instruct a decoder to match a value in a given field. |
optional_field/3 | Decodes a field or uses a default value when the field is missing. |
at/2 | Instruct a decode to match a value in a nested path. |
prop/2 | Decode a field as a property. |
prop_list/1 | Decode a proplist by matching fields on a JSON object. |
to_map/1 | Decode arbitrary JSON into a map() |
list/1 | Decode a JSON list using a decoder. |
nonempty_list/1 | Decode a nonempty JSON list into a nonempty list [T, ..] |
index/2 | Decode a single index in a JSON list using the specified decoder. |
sequence/1 | Sequence a bunch of decoders, succeeding with the collected values. |
set/1 | Decode a JSON list into an erlang sets:set(T) |
map/2 | Manipulate the values produced by a decoder with a function. |
chain/2 | Chain one or more functions that create decoders onto a decoder. |
fail/1 | Create a decoder that always fails with the provided term. |
succeed/1 | Create a decoder that always succeeds with the provided term. |
mapn/2 | Apply an n-ary function against a list of n decoders. |
exactly/2 | Decoding succeeds if the decoder produces exactly the supplied value. |
one_of/1 | Try a bunch of decoders. |
decode/2 | Equivalent to dj:decode(Json, Decoder, [{labels, attempt_atom}]). |
decode/3 | Run a decoder(T) against arbirary JSON. |
binary() -> decoder(binary())
Decodes a JSON string as a binary()
.
{ok, <<"Hi there">>} = dj:decode(<<"\"Hi there\"">>, dj:binary()).
If the specified JSON is not a string (it might, for example, be an integer),
this will return an error indicating that an unexpected type was found -
including the actual value that was found instead. This value will be a
jsx:json_term()
.
{error, {dj_errors, [{unexpected_type, binary, 123}]}} = dj:decode(<<"123">>, dj:binary()).
integer() -> decoder(integer())
Decodes a JSON integer as an integer()
{ok, 123} = dj:decode(<<"123">>, dj:integer()). {error, {dj_errors, [{unexpected_type, integer, true}]}} = dj:decode(<<"true">>, dj:integer()).
See also: float/0, integer/2, neg_integer/0, non_neg_integer/0, pos_integer/0.
pos_integer() -> decoder(pos_integer())
Decodes a strictly positive JSON integer as a pos_integer()
See also: float/0, integer/0, neg_integer/0, non_neg_integer/0.
neg_integer() -> decoder(neg_integer())
Decodes a negative JSON integer as a neg_integer()
See also: float/0, integer/0, non_neg_integer/0, pos_integer/0.
non_neg_integer() -> decoder(non_neg_integer())
Decodes a positive JSON integer as a non_neg_integer()
See also: float/0, integer/0, neg_integer/0, pos_integer/0.
float() -> decoder(float())
Decodes a JSON number as a float()
Note that JSON does not have a separate floating point type. As such, integers in JSON will be cast to floats by this function.
{ok, 123.0} = dj:decode(<<"123">>, dj:float()).
See also: integer/0, neg_integer/0, non_neg_integer/0, pos_integer/0.
null() -> decoder(null)
Equivalent to null(null).
null(V) -> decoder(V)
Decodes null
into an arbitrary value.
This can be used to convert null
to a specific value, like undefined
or a
default value that makes sense for your application.
{ok, foo} = dj:decode(<<"null">>, dj:null(foo)).
See also: null/0.
boolean() -> decoder(boolean())
Decodes a JSON true
or false
to a boolean()
{ok, true} = dj:decode(<<"true">>, dj:boolean()). {ok, false} = dj:decode(<<"false">>, dj:boolean()). {error, _} = dj:decode(<<"null">>, dj:boolean()).
value() -> decoder(jsx:json_term())
Extracts a raw jsx:json_term()
atom() -> decoder(atom())
Decodes a JSON value to an atom()
If the JSON value is true
, false
or null
, this returns an erlang atom
true
, false
or null
. If the JSON value is a string,
binary_to_atom(Json, utf8)
is used to turn it into an atom.
See also: atom/1, existing_atom/0.
atom(Allowed :: [atom(), ...]) -> decoder(atom())
Decodes a JSON value to one of a set of predefined atoms
This is a safer alternative toatom()
, as it not only allows whitelisting
allowed values, but can also prevent creating new atoms.
existing_atom() -> decoder(atom())
Decodes a JSON value to an existing atom
Note that Erlang, in some cases, may optimize atoms away. For example, if an atom is only every used in anatom_to_binary(some_atom)
call, the
some_atom
atom may not "exist".
See also: atom/1.
existing_atom(Allowed :: [atom(), ...]) -> decoder(atom())
Equivalent to atom(Allowed).
email() -> decoder(binary())
Decodes a JSON string as a binary()
if and only if it looks like an
email address.
{ok, <<"ilias@truqu.com">>} = dj:decode(<<"\"ilias@truqu.com\"">>, dj:email()).
If the specified JSON is not a string, this will fail with an
unexpected_type
error (expecing a binary
). If the specified JSON is a
string but does not look like an email address, this will fail with a custom
not_an_email
error.
E = {dj_errors, [{custom, {not_an_email, <<"foo@bar">>}}]}, {error, E} = dj:decode(<<"\"foo@bar\"">>, dj:email()).
full_date_tuple(X1 :: rfc3339) -> decoder(calendar:date())
uuid(X1 :: v4) -> decoder(binary())
Decode a UUIDv4 from a JSON string
integer(Min :: integer(), Max :: integer()) -> decoder(integer())
Decode a bounded integer from JSON
Occasionally, you may want to decode a JSON integer only when it sits between certain bounds. Both the upper and lower bound are inclusive.
-spec score() -> dj:decoder(1..10) score() -> dj:integer(1, 10). {ok, 5} = dj:decode(<<"5">>, score()). E = {dj_errors, [{custom, {integer_out_of_bounds, 1, 10, 0}}]}, {error, E} = dj:decode(<<"0", score()).
See also: integer/0.
Equivalent to nullable(Decoder, null).
Decode a nullable value
Sometimes, we explicitly want to allow null
. In such a case, making that
clear by wrapping a decoder with nullable/1
can help readability.
-spec score() -> dj:decoder(1..10) score() -> dj:integer(1, 10). {ok, 5} = dj:decode(<<"5">>, nullable(score())). {ok, null} = dj:decode(<<"null">>, nullable(score())). E = {dj_error, [ {unexpected_type, integer, true} , {unexpected_type, null, true} ]}, {error, E} = dj:decode(<<"true", nullable(score())).
Equivalent to nullable(Decoder, null).
Decode a nullable value
Sometimes, we explicitly want to allow null
but use a default value. In
such a case, making that clear by wrapping a decoder with nullable/2
can
help readability.
-spec score() -> dj:decoder(1..10) score() -> dj:integer(1, 10). {ok, 4} = dj:decode(<<"4">>, nullable(score(), 5)). {ok, 5} = dj:decode(<<"null">>, nullable(score(), 5)). E = {dj_error, [ {unexpected_type, integer, true} , {unexpected_type, null, true} ]}, {error, E} = dj:decode(<<"true", nullable(score(), 5)).
Instruct a decoder to match a value in a given field
Dec = dj:field(foo, dj:binary()), {ok, <<"bar">>} = dj:decode(<<"{\"foo\": \"bar\"}">>, Dec), Error = {unexpected_type, binary, null}, InField = {in_field, foo, Error}, {error, {dj_errors, [InField]}} = dj:decode(<<"{\"foo\": null}">>, Dec), Missing = {missing_field, foo, #{}}, {error, {dj_erros, [Missing]}} = dj:decode(<<"{}">>, Dec).
See also: at/2, prop/2, prop_list/1, to_map/1.
Decodes a field or uses a default value when the field is missing
Note that if the field is present but malformed according to the decoder, decoding will fail. If we're not working in the context of a map/object, decoding also fails.
Dec = dj:optional_field(foo, dj:binary(), <<"default">>), {ok, <<"bar">>} = dj:decode(<<"{\"foo\": \"bar\"}">>, Dec), {ok, <<"default">>} = dj:decode(<<"{}">>, Dec), Error = {unexpected_type, binary, null}, InField = {in_field, foo, [Error]}, {error, {dj_error, [InField]}} = dj:decode(<<"{\"foo\": null}">>, Dec), NotMap = {unexpected_type, map, <<"foo">>}, {error, {dj_error, [NotMap]}} = dj:decode(<<"\"foo\"">>, Dec).
Instruct a decode to match a value in a nested path
{ok, null} = dj:decode(<<"null">>, dj:at([], dj:null())). Dec = dj:at([foo, bar], dj:decode(dj:integer())), Json = <<"{\"foo\": {\"bar\": 123}}">>, {ok, 123} = dj:decode(Json, Dec).
See also: field/2, prop/2, prop_list/1, to_map/1.
Decode a field as a property
Similar tofield/2
but adds the fieldname to the decoded entry.
Convenient for decoding into a proplist.
See also: field/2, prop_list/1.
Decode a proplist by matching fields on a JSON object
to_map(MapSpec) -> decoder(MapResult)
Decode arbitrary JSON into a map()
Dec = dj:to_map(#{ x => dj:index(0, dj:integer()) , y => dj:index(1, dj:integer()) , z => dj:index(2, dj:integer()) }), Json = <<"[1, 6, 2]">>, {ok, #{x := 1, y := 6, z := 2}} = dj:decode(Json, Dec).
See also: field/2.
Decode a JSON list using a decoder
nonempty_list(T) -> decoder([T, ...])
Decode a nonempty JSON list into a nonempty list [T, ..]
Decode a single index in a JSON list using the specified decoder
Sequence a bunch of decoders, succeeding with the collected values
Decode a JSON list into an erlang sets:set(T)
Manipulate the values produced by a decoder with a function
Dec = dj:map(fun string:uppercase/1, dj:binary()), Json = <<"\"hello world\"">>, {ok, <<"HELLO WORLD">>} = dj:decode(Json, Dec).
chain(DecoderA :: decoder(A), ToDecoderB) -> decoder(B)
ToDecoderB = ToDecB | [ToDecB]
ToDecB = fun((A) -> decoder(B))
Chain one or more functions that create decoders onto a decoder
This has many uses. One possible use is to handle data that may represent different things:
-type shape() :: {square, pos_integer()} | {oblong, pos_integer(), pos_integer()}. -spec square() -> dj:decoder(shape()). square() -> dj:map(fun (S) -> {square, S} end, dj:field(side, dj:pos_integer())). -spec oblong() -> dj:decoder(shape()). oblong() -> dj:map( fun(L, W) -> {oblong, L, W} end , [ dj:field(length, dj:pos_integer()) , dj:field(width, dj:pos_integer()) ] ). -spec shape(square | oblong) -> dj:decoder(shape()). shape(square) -> square(); shape(oblong) -> oblong(). -spec shape() -> dj:decoder(shape()). shape() -> dj:chain(dj:field(type, dj:atom([square, oblong])), fun shape/1). {ok, {square, 12}} = dj:decode(<<"{\"type\": \"square\", \"side\": 12}">>, shape()).
Occasionally, you may want to chain an operation that doesn't result in a
different decoder, but rather results in either failure or success. In that
case, use succeed/1
or fail/1
.
See also: fail/1, map/2, succeed/1.
fail(E :: term()) -> decoder(V :: term())
Create a decoder that always fails with the provided term
Dec = dj:fail(no_more_bananas), {error, {dj_errors, [{custom, no_more_bananas}]}} = dj:decode(<<"true">>, Dec).Mostly useful when combined with
chain
.
succeed(T) -> decoder(T)
Create a decoder that always succeeds with the provided term
Dec = dj:one_of([ dj:field(online, dj:boolean()) , dj:succeed(false) ]), Json = << "[ {\"online\": true}" , ", {\"online\": false}" , ", {} ]" >>, {ok, [true, false, false]} = dj:decode(Json, dj:list(Dec)).This function is also useful for hardcoding values in
to_map/1
,
handling failure and success in chain/2
and - as demonstrated -
defaulting values using one_of/1
.
See also: chain/2, fail/1, one_of/1, to_map/1.
Apply an n-ary function against a list of n decoders
Dec = dj:mapn( fun (X, Y, Z) -> {X, Y, Z} end , [ dj:field(major, dj:pos_integer()) , dj:field(minor, dj:non_neg_integer()) , dj:field(patch, dj:non_neg_integer()) ] ), Json = << "{ \"major\": 123" , ", \"minor\": 66" , ", \"patch\": 0" , "}">>, {ok, {123, 66, 0}} = dj:decode(Json, Dec).
When the arity doesn't match, a custom error is returned with the expected arity (based on the number of decoders passed) and the actual arity of the passed function.
Dec = dj:mapn( fun (X, Y) -> {X, Y, 0} end , [ dj:field(major, dj:pos_integer()) , dj:field(minor, dj:non_neg_integer()) , dj:field(patch, dj:non_neg_integer()) ] ), Json = << "{ \"major\": 123" , ", \"minor\": 66" , ", \"patch\": 0" , "}">>, {error, {dj_errors, [{custom, {arity_mismatch, 3, 2}}]}} = dj:decode(Json, Dec).
Decoding succeeds if the decoder produces exactly the supplied value.
This can, for example, be used when a certain field is used to switch between different decoders.Try a bunch of decoders. The first one to succeed will be used.
If all decoders fails, the errors are accumulated.
Dec = dj:one_of([dj:binary(), dj:integer()]), {ok, <<"foo">>} = dj:decode(<<"\"foo\"">>, Dec), {ok, 123} = dj:decode(<<"123">>, Dec), {error, _} = dj:decode(<<"null">>, Dec).
Equivalent to dj:decode(Json, Decoder, [{labels, attempt_atom}]).
decode(Json, Decoder :: decoder(T), Opts) -> result(T, {dj_error, errors()})
Json = jsx:json_text()
Opts = [Opt]
Opt = jsx:option() | labels | {labels, LabelOpt}
LabelOpt = atom | attempt_atom | binary | existing_atom
Run a decoder(T)
against arbirary JSON.
The resulting result(T, error())
is either a tuple {ok, T}
or a
tuple {error, error()}
where error()
represents whatever went wrong
during the decoding/validation/transformation process.
Opts` are passed on to `jsx:decode/2
. The option return_maps
is always
added by dj and does not need to be specified manually.
decoder(T)
s and functions that help
with composition are discussed individually.
Generated by EDoc