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