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/0s
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. |
| field/2 | Instruct a decoder to match a value in a given field. |
| 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.
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.
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).
decode(Json, Decoder :: decoder(T)) -> result(T, {dj_error, errors()})
Json = jsx:json_text()
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