jsone

hex.pm versionBuild StatusCode CoverageLicense: MIT

An Erlang library for encoding, decoding JSON data.

Features

QuickStart

# clone
$ git clone git://github.com/sile/jsone.git
$ cd jsone
# compile
$ make compile
# run tests
$ make eunit
# dialyze
$ make dialyze
# Erlang shell
$ make start
1> jsone:decode(<<"[1,2,3]">>).
[1,2,3]

Enable HiPE

If you want to use HiPE compiled version, please add following code to your rebar.config.

{overrides,
[
{override, jsone, [{erl_opts, [{d, 'ENABLE_HIPE'}, inline]}]}
]}.

or use native profile. The make command supports profile as well. For example:

$ make start profile=native

Usage Example

%% Decode
> jsone:decode(<<"[1,2,3]">>).
[1,2,3]
> jsone:decode(<<"{\"1\":2}">>).
#{<<"1">> => 2}
> jsone:decode(<<"{\"1\":2}">>, [{object_format, tuple}]). % tuple format
{[{<<"1">>, 2}]}
> jsone:decode(<<"{\"1\":2}">>, [{object_format, proplist}]). % proplist format
[{<<"1">>, 2}]
> jsone:try_decode(<<"[1,2,3] \"next value\"">>). % try_decode/1 returns remaining (unconsumed binary)
{ok,[1,2,3],<<" \"next value\"">>}
% error: raises exception
> jsone:decode(<<"1.x">>).
** exception error: bad argument
in function jsone_decode:number_fraction_part_rest/6
called as jsone_decode:number_fraction_part_rest(<<"x">>,1,1,0,[],<<>>)
in call from jsone:decode/1 (src/jsone.erl, line 71)
% error: returns {error, Reason}
> jsone:try_decode(<<"1.x">>).
{error,{badarg,[{jsone_decode,number_fraction_part_rest,
[<<"x">>,1,1,0,[],<<>>],
[{line,228}]}]}}
%% Encode
> jsone:encode([1,2,3]).
<<"[1,2,3]">>
> jsone:encode(#{<<"key">> => <<"value">>}). % map format
> jsone:encode({[{<<"key">>, <<"value">>}]}). % tuple format
> jsone:encode([{<<"key">>, <<"value">>}]). % proplist format
<<"{\"key\":\"value\"}">>
> jsone:encode(#{key => <<"value">>}). % atom key is allowed
<<"{\"key\":\"value\"}">>
% error: raises exception
> jsone:encode(#{123 => <<"value">>}). % non binary|atom key is not allowed
** exception error: bad argument
in function jsone_encode:object_members/3
called as jsone_encode:object_members([{123,<<"value">>}],[],<<"{">>)
in call from jsone:encode/1 (src/jsone.erl, line 97)
% error: returns {error, Reason}
> jsone:try_encode({[{123, <<"value">>}]}).
{error,{badarg,[{jsone_encode,object_members,
[[{123,<<"value">>}],[],<<"{">>],
[{line,138}]}]}}
% 'object_key_type' option allows non-string object key
> jsone:encode({[{123, <<"value">>}]}, [{object_key_type, scalar}]).
<<"{\"123\":\"value\"}">>
% 'undefined_as_null' option allows encoding atom undefined as null
> jsone:encode(undefined,[undefined_as_null]).
<<"null">>
%% Pretty Print
> Data = [true, #{<<"1">> => 2, <<"array">> => [[[[1]]], #{<<"ab">> => <<"cd">>}, [], #{}, false]}, null].
> io:format("~s\n", [jsone:encode(Data, [{indent, 2}, {space, 1}])]).
[
true,
{
"1": 2,
"array": [
[
[
[
1
]
]
],
{
"ab": "cd"
},
[],
{},
false
]
},
null
]
ok
%% Number Format
> jsone:encode(1). % integer
<<"1">>
> jsone:encode(1.23). % float
<<"1.22999999999999998224e+00">> % default: scientific notation
> jsone:encode(1.23, [{float_format, [{decimals, 4}]}]). % decimal notation
<<"1.2300">>
> jsone:encode(1.23, [{float_format, [{decimals, 4}, compact]}]). % compact decimal notation
<<"1.23">>
%% If you want to safely cast object keys to atoms, the `attempt_atom' option will help.
> jsone:decode(<<"{\"hello\": \"world\"}">>, [{keys, attempt_atom}]).
#{<<"hello">> => <<"world">>} % There is no atom named "hello", so the key is decoded as binary.
> hello. % Create "hello" atom.
hello
> jsone:decode(<<"{\"hello\": \"world\"}">>, [{keys, attempt_atom}]).
#{hello => <<"world">>} % Now, the key is decoded as atom.

Data Mapping (Erlang <=> JSON)

Erlang JSON Erlang
=================================================================================================
null -> null -> null
undefined -> null -> undefined % undefined_as_null
true -> true -> true
false -> false -> false
<<"abc">> -> "abc" -> <<"abc">>
abc -> "abc" -> <<"abc">> % non-special atom is regarded as a binary
{{2010,1,1},{0,0,0}} -> "2010-01-01T00:00:00Z" -> <<"2010-01-01T00:00:00Z">> % datetime*
{{2010,1,1},{0,0,0.0}} -> "2010-01-01T00:00:00.000Z" -> <<"2010-01-01T00:00:00.000Z">> % datetime*
123 -> 123 -> 123
123.4 -> 123.4 -> 123.4
[1,2,3] -> [1,2,3] -> [1,2,3]
{[]} -> {} -> {[]} % object_format=tuple
{[{key, <<"val">>}]} -> {"key":"val"} -> {[{<<"key">>, <<"val">>}]} % object_format=tuple
[{}] -> {} -> [{}] % object_format=proplist
[{<<"key">>, val}] -> {"key":"val"} -> [{<<"key">>, <<"val">>}] % object_format=proplist
#{} -> {} -> #{} % object_format=map
#{key => val} -> {"key":"val"} -> #{<<"key">> => <<"val">>} % object_format=map
{{json, IOList}} -> Value -> ~~~ % UTF-8 encoded term**
{{json_utf8, Chars}} -> Value -> ~~~ % Unicode code points**

* see jsone:datetime_encode_format()

** {json, IOList} and {json_utf8, Chars} allows inline already encoded JSON values. For example, you obtain JSON encoded data from database so you don't have to decode it first and encode again. See jsone:json_term().

API

See EDoc Document

Benchmark

The results of poison benchmarking.

See the BENCHMARK.md file for more information.

EncoderBench Result

Non HiPE:

jiffyjsonepoisonjazzjsx
maps7.23 μs/op10.64 μs/op (2)13.58 μs/op19.30 μs/op29.28 μs/op
lists210.40 μs/op157.39 μs/op (3)109.30 μs/op201.82 μs/op357.25 μs/op
strings*98.80 μs/op595.63 μs/op (5)416.78 μs/op399.89 μs/op262.18 μs/op
string escaping*144.01 μs/op732.44 μs/op (2)1318.82 μs/op1197.06 μs/op1324.04 μs/op
large value**408.03 μs/op1556.85 μs/op (3)1447.71 μs/op1824.05 μs/op2184.59 μs/op
pretty print**420.94 μs/op1686.55 μs/op (3)1534.74 μs/op2041.22 μs/op5533.04 μs/op

HiPE:

jiffyjsonepoisonjazzjsx
maps7.69 μs/op6.12 μs/op (1)12.32 μs/op22.90 μs/op27.03 μs/op
lists207.75 μs/op69.93 μs/op (1)79.04 μs/op229.95 μs/op278.01 μs/op
strings*96.67 μs/op321.69 μs/op (5)142.43 μs/op310.10 μs/op179.96 μs/op
string escaping*146.85 μs/op317.10 μs/op (2)1277.54 μs/op1311.85 μs/op767.67 μs/op
large value**409.73 μs/op664.34 μs/op (2)806.24 μs/op1630.21 μs/op1777.62 μs/op
pretty print**419.55 μs/op724.28 μs/op (2)844.76 μs/op1888.71 μs/op4872.34 μs/op

* binary representation of UTF-8-demo.txt
** generated.json

ParserBench Result

Non HiPE:

jiffyjsonepoisonjsx
json value*544.84 μs/op1364.38 μs/op (2)1401.35 μs/op1844.55 μs/op
UTF-8 unescaping**63.01 μs/op399.38 μs/op (4)249.70 μs/op281.84 μs/op

HiPE:

jiffyjsonepoisonjsx
json value*542.77 μs/op561.15 μs/op (2)751.36 μs/op1435.10 μs/op
UTF-8 unescaping**62.42 μs/op92.63 μs/op (2)118.97 μs/op172.07 μs/op

* generated.json
** UTF-8-demo.txt

License

This library is released under the MIT License.

See the COPYING file for full license information.