Jsx
an erlang application for consuming, producing and manipulating json. inspired by yajl
Install / Use
/learn @talentdeficit/JsxREADME
jsx (v3.0.0)
an erlang application for consuming, producing and manipulating [json][json]. inspired by [yajl][yajl]
jsx is built via [rebar3][rebar3]
current status:
jsx is released under the terms of the [MIT][MIT] license
copyright 2010-2016 alisdair sullivan
index
quickstart
to add to a rebar3 project
Add to rebar.config
...
{erl_opts, [debug_info]}.
{deps, [
...
{jsx, "~> 3.0"}
]}.
...
to build the library and run tests
$ rebar3 compile
$ rebar3 eunit
to convert a utf8 binary containing a json string into an erlang term
1> jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, []).
#{<<"awesome">> => true,<<"library">> => <<"jsx">>}
2> jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, [{return_maps, false}]).
[{<<"library">>,<<"jsx">>},{<<"awesome">>,true}]
3> jsx:decode(<<"[\"a\",\"list\",\"of\",\"words\"]">>).
[<<"a">>, <<"list">>, <<"of">>, <<"words">>]
to convert an erlang term into a utf8 binary containing a json string
1> jsx:encode(#{<<"library">> => <<"jsx">>, <<"awesome">> => true}).
<<"{\"awesome\":true,\"library\":\"jsx\"}">>
2> jsx:encode([{<<"library">>,<<"jsx">>},{<<"awesome">>,true}]).
<<"{\"library\": \"jsx\", \"awesome\": true}">>
3> jsx:encode([<<"a">>, <<"list">>, <<"of">>, <<"words">>]).
<<"[\"a\",\"list\",\"of\",\"words\"]">>
to check if a binary or a term is valid json
1> jsx:is_json(<<"[\"this is json\"]">>).
true
2> jsx:is_json("[\"this is not\"]").
false
3> jsx:is_term([<<"this is a term">>]).
true
4> jsx:is_term([this, is, not]).
false
to minify some json
1> jsx:minify(<<"{
\"a list\": [
1,
2,
3
]
}">>).
<<"{\"a list\":[1,2,3]}">>
to prettify some json
1> jsx:prettify(<<"{\"a list\":[1,2,3]}">>).
<<"{
\"a list\": [
1,
2,
3
]
}">>
description
jsx is an erlang application for consuming, producing and manipulating [json][json]
jsx follows the json [spec][rfc4627] as closely as possible with allowances for real world usage
jsx is pragmatic. the json spec allows extensions so jsx extends the spec in a
number of ways. see the section on strict in options below though
json has no official comments but this parser allows c/c++ style comments.
anywhere whitespace is allowed you can insert comments (both // ... and /* ... */)
some particularly irresponsible json emitters leave trailing commas at the end of objects or arrays. jsx allows a single trailing comma in input. multiple commas in any position or a preceding comma are still errors
all jsx decoder input should be utf8 encoded binaries. sometimes you get binaries
that are almost but not quite valid utf8 whether due to improper escaping or poor
encoding. jsx replaces invalid codepoints and poorly formed sequences with the
unicode replacement character (u+FFFD) but does it's best to return something
comprehensible
json only allows keys and strings to be delimited by double quotes (u+0022) but
javascript allows them to be delimited by single quotes (u+0027) as well. jsx
follows javascript in this. strings that start with single quotes can contain double
quotes but must end with single quotes and must escape any single quotes they contain
json and jsx only recognize escape sequences as outlined in the json spec. it just ignores bad escape sequences leaving them in strings unaltered
json <-> erlang mapping
json | erlang
--------------------------------|--------------------------------
number | integer() and float()
string | binary() and atom()
true, false and null | true, false and null
array | [] and [JSON]
object | #{}, [{}] and [{binary() OR atom() OR integer(), JSON}]
see below | datetime()
-
numbers
javascript and thus json represent all numeric values with floats. there's no reason for erlang -- a language that supports arbitrarily large integers -- to restrict all numbers to the ieee754 range
whenever possible, jsx will interpret json numbers that look like integers as integers. other numbers will be converted to erlang's floating point type, which is nearly but not quite iee754. negative zero is not representable in erlang (zero is unsigned in erlang and
0is equivalent to-0) and will be interpreted as regular zero. numbers not representable are beyond the concern of this implementation, and will result in parsing errorswhen converting from erlang to json, floats are represented with their shortest representation that will round trip without loss of precision. this means that some floats may be superficially dissimilar (although functionally equivalent). for example,
1.0000000000000001will be represented by1.0 -
strings
json strings must be unicode encoded binaries or erlang atoms. in practice, because jsx only accepts
utf8binaries all binary strings must beutf8. in addition to being unicode json strings restrict a number of codepoints and define a number of escape sequencesjson string escapes of the form
\uXXXXwill be converted to their equivalent codepoints during parsing. this means control characters and other codepoints disallowed by the json spec may be encountered in resulting strings. the utf8 restriction means the surrogates are explicitly disallowed. if a string contains escaped surrogates (u+d800tou+dfff) they are interpreted but only when they form valid surrogate pairs. surrogates encountered otherwise are replaced with the replacement codepoint (u+fffd)all erlang strings are represented by valid
utf8encoded binaries. the encoder will check strings for conformance. badly formedutf8sequences may be replaced with the replacement codepoint (u+fffd) according to the unicode specthis implementation performs no normalization on strings beyond that detailed here. be careful when comparing strings as equivalent strings may have different
utf8encodings -
true, false and null
the json primitives
true,falseandnullare represented by the erlang atomstrue,falseandnull. surprise -
arrays
json arrays are represented with erlang lists of json values as described in this section
-
objects
json objects are represented by erlang maps.
-
datetime
erlang datetime tuples (
{{Year, Month, Day}, {Hour, Min, Sec}}) as returned fromerlang:localtime/0are automatically encoded as [iso8601][iso8601] strings and are assumed to be UTC time. no conversion is attempted of json [iso8601][iso8601] strings in decoded json
incomplete input
jsx can handle incomplete json texts. if the option stream is passed to the decoder
or parser and if a partial json text is parsed, rather than returning a term from
your callback handler, jsx returns {incomplete, F} where F is a function with
an identical API to the anonymous fun returned from decoder/3, encoder/3 or
parser/3. it retains the internal state of the parser at the point where input
was exhausted. this allows you to parse as you stream json over a socket or file
descriptor, or to parse large json texts without needing to keep them entirely in
memory
however, it is important to recognize that jsx is conservative by default. jsx will
not consider the parsing complete even when input is exhausted and the json text is
unambiguously incomplete. to end parsing call the incomplete function with the
argument end_stream (or end_json) like:
1> {incomplete, F} = jsx:decode(<<"[">>, [stream]).
{incomplete,#Fun<jsx_decoder.1.122947756>}
2> F(end_stream). % can also be `F(end_json)`
** exception error: bad argument
3> {incomplete, G} = F(<<"]">>).
{incomplete,#Fun<jsx_decoder.1.122947756>}
4> G(end_stream). % can also be `G(end_json)`
[]
data types
json_term()
json_term() = [json_term()]
| [{binary() | atom() | integer(), json_term()}]
| #{} % map of any size, not just the empty map
| true
| false
| null
| integer()
| float()
| binary()
| atom()
| datetime()
the erlang representation of json. binaries should be utf8 encoded, or close
at least
json_text()
json_text() = binary()
a utf8 encoded binary containing a json string
event()
event() = start_object
| end_object
| start_array
| end_array
| {key, binary()}
| {string, binary()}
| {integer, integer()}
| {float, float()}
| {literal, true}
| {literal, false}
| {literal, null}
| end_json
the subset of token() emitted by th
