2010-05-25 21:26:55 -07:00
|
|
|
%% The MIT License
|
|
|
|
|
2013-03-10 20:30:24 -07:00
|
|
|
%% Copyright (c) 2010-2013 alisdair sullivan <alisdairsullivan@yahoo.ca>
|
2010-05-25 21:26:55 -07:00
|
|
|
|
|
|
|
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
%% of this software and associated documentation files (the "Software"), to deal
|
|
|
|
%% in the Software without restriction, including without limitation the rights
|
|
|
|
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
%% copies of the Software, and to permit persons to whom the Software is
|
|
|
|
%% furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
%% The above copyright notice and this permission notice shall be included in
|
|
|
|
%% all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
%% THE SOFTWARE.
|
|
|
|
|
|
|
|
|
2010-05-18 13:01:49 -07:00
|
|
|
-module(jsx).
|
|
|
|
|
2012-05-23 21:57:01 -07:00
|
|
|
-export([encode/1, encode/2, decode/1, decode/2]).
|
2012-03-04 18:40:00 -08:00
|
|
|
-export([is_json/1, is_json/2, is_term/1, is_term/2]).
|
2012-05-12 23:28:48 +00:00
|
|
|
-export([format/1, format/2, minify/1, prettify/1]).
|
2012-05-23 06:47:58 -07:00
|
|
|
-export([encoder/3, decoder/3, parser/3]).
|
2013-03-05 21:10:33 -08:00
|
|
|
-export([resume/3]).
|
2013-03-05 20:02:52 -08:00
|
|
|
|
|
|
|
-export_type([json_term/0, json_text/0, token/0]).
|
|
|
|
-export_type([encoder/0, decoder/0, parser/0, internal_state/0]).
|
2011-11-23 20:54:10 -08:00
|
|
|
|
2012-11-06 08:30:00 -06:00
|
|
|
|
2011-11-23 20:54:10 -08:00
|
|
|
-ifdef(TEST).
|
2013-10-20 21:06:51 +00:00
|
|
|
%% data and helper functions for tests
|
|
|
|
-export([test_cases/0]).
|
|
|
|
-export([init/1, handle_event/2]).
|
2011-11-23 20:54:10 -08:00
|
|
|
-endif.
|
2011-10-21 18:16:16 -07:00
|
|
|
|
|
|
|
|
2013-09-02 08:54:10 +01:00
|
|
|
-type json_term()
|
|
|
|
:: [{binary() | atom(), json_term()}]
|
|
|
|
| [json_term()]
|
2012-05-22 21:51:23 -07:00
|
|
|
| true
|
|
|
|
| false
|
|
|
|
| null
|
|
|
|
| integer()
|
|
|
|
| float()
|
2013-12-12 11:31:38 -08:00
|
|
|
| binary()
|
|
|
|
| atom().
|
2012-05-22 21:51:23 -07:00
|
|
|
|
2012-05-23 06:47:58 -07:00
|
|
|
-type json_text() :: binary().
|
2010-09-15 21:30:25 -07:00
|
|
|
|
2013-01-22 12:41:56 -08:00
|
|
|
-spec encode(Source::json_term()) -> json_text() | {incomplete, encoder()}.
|
2013-02-12 11:54:42 -08:00
|
|
|
-spec encode(Source::json_term(), Config::jsx_to_json:config()) -> json_text() | {incomplete, encoder()}.
|
2011-11-23 20:54:10 -08:00
|
|
|
|
2012-05-23 21:57:01 -07:00
|
|
|
encode(Source) -> encode(Source, []).
|
2013-02-12 11:54:42 -08:00
|
|
|
encode(Source, Config) -> jsx_to_json:to_json(Source, Config).
|
2012-01-31 20:44:35 -08:00
|
|
|
|
2012-03-05 20:37:22 -08:00
|
|
|
|
2013-06-04 01:12:25 +00:00
|
|
|
-spec decode(Source::json_text()) -> json_term() | {incomplete, decoder()}.
|
|
|
|
-spec decode(Source::json_text(), Config::jsx_to_term:config()) -> json_term() | {incomplete, decoder()}.
|
|
|
|
|
|
|
|
decode(Source) -> decode(Source, []).
|
|
|
|
decode(Source, Config) -> jsx_to_term:to_term(Source, Config).
|
2011-10-21 18:16:16 -07:00
|
|
|
|
2011-10-24 22:51:39 -07:00
|
|
|
|
2013-01-22 12:41:56 -08:00
|
|
|
-spec format(Source::json_text()) -> json_text() | {incomplete, decoder()}.
|
2013-02-12 11:54:42 -08:00
|
|
|
-spec format(Source::json_text(), Config::jsx_to_json:config()) -> json_text() | {incomplete, decoder()}.
|
2011-11-29 19:56:00 -08:00
|
|
|
|
|
|
|
format(Source) -> format(Source, []).
|
2013-02-12 11:54:42 -08:00
|
|
|
format(Source, Config) -> jsx_to_json:format(Source, Config).
|
2011-11-29 19:56:00 -08:00
|
|
|
|
|
|
|
|
2013-01-22 12:41:56 -08:00
|
|
|
-spec minify(Source::json_text()) -> json_text() | {incomplete, decoder()}.
|
2012-05-12 23:28:48 +00:00
|
|
|
|
|
|
|
minify(Source) -> format(Source, []).
|
|
|
|
|
|
|
|
|
2013-01-22 12:41:56 -08:00
|
|
|
-spec prettify(Source::json_text()) -> json_text() | {incomplete, decoder()}.
|
2012-05-12 23:28:48 +00:00
|
|
|
|
|
|
|
prettify(Source) -> format(Source, [space, {indent, 2}]).
|
|
|
|
|
|
|
|
|
2012-05-22 21:51:23 -07:00
|
|
|
-spec is_json(Source::any()) -> true | false.
|
2013-02-12 11:54:42 -08:00
|
|
|
-spec is_json(Source::any(), Config::jsx_verify:config()) -> true | false.
|
2011-11-23 20:54:10 -08:00
|
|
|
|
|
|
|
is_json(Source) -> is_json(Source, []).
|
2013-02-12 11:54:42 -08:00
|
|
|
is_json(Source, Config) -> jsx_verify:is_json(Source, Config).
|
2011-10-24 22:51:39 -07:00
|
|
|
|
|
|
|
|
2012-03-04 18:40:00 -08:00
|
|
|
-spec is_term(Source::any()) -> true | false.
|
2013-02-12 11:54:42 -08:00
|
|
|
-spec is_term(Source::any(), Config::jsx_verify:config()) -> true | false.
|
2012-03-04 18:40:00 -08:00
|
|
|
|
|
|
|
is_term(Source) -> is_term(Source, []).
|
2013-02-12 11:54:42 -08:00
|
|
|
is_term(Source, Config) -> jsx_verify:is_term(Source, Config).
|
2012-03-04 18:40:00 -08:00
|
|
|
|
|
|
|
|
2012-05-23 06:47:58 -07:00
|
|
|
-type decoder() :: fun((json_text() | end_stream) -> any()).
|
|
|
|
|
2013-02-12 11:54:42 -08:00
|
|
|
-spec decoder(Handler::module(), State::any(), Config::list()) -> decoder().
|
2012-03-04 15:46:41 -08:00
|
|
|
|
2013-02-12 11:54:42 -08:00
|
|
|
decoder(Handler, State, Config) -> jsx_decoder:decoder(Handler, State, Config).
|
2012-03-04 15:46:41 -08:00
|
|
|
|
|
|
|
|
2012-05-23 06:47:58 -07:00
|
|
|
-type encoder() :: fun((json_term() | end_stream) -> any()).
|
|
|
|
|
2013-02-12 11:54:42 -08:00
|
|
|
-spec encoder(Handler::module(), State::any(), Config::list()) -> encoder().
|
2012-03-04 15:46:41 -08:00
|
|
|
|
2013-02-12 11:54:42 -08:00
|
|
|
encoder(Handler, State, Config) -> jsx_encoder:encoder(Handler, State, Config).
|
2012-03-04 15:46:41 -08:00
|
|
|
|
|
|
|
|
2012-05-23 06:47:58 -07:00
|
|
|
-type token() :: [token()]
|
|
|
|
| start_object
|
|
|
|
| end_object
|
|
|
|
| start_array
|
|
|
|
| end_array
|
|
|
|
| {key, binary()}
|
|
|
|
| {string, binary()}
|
|
|
|
| binary()
|
|
|
|
| {number, integer() | float()}
|
|
|
|
| {integer, integer()}
|
|
|
|
| {float, float()}
|
|
|
|
| integer()
|
|
|
|
| float()
|
|
|
|
| {literal, true}
|
|
|
|
| {literal, false}
|
|
|
|
| {literal, null}
|
|
|
|
| true
|
|
|
|
| false
|
|
|
|
| null
|
|
|
|
| end_json.
|
2012-11-06 08:30:00 -06:00
|
|
|
|
2012-05-23 06:47:58 -07:00
|
|
|
|
|
|
|
-type parser() :: fun((token() | end_stream) -> any()).
|
|
|
|
|
2013-02-12 11:54:42 -08:00
|
|
|
-spec parser(Handler::module(), State::any(), Config::list()) -> parser().
|
2012-05-23 06:47:58 -07:00
|
|
|
|
2013-03-05 20:02:52 -08:00
|
|
|
parser(Handler, State, Config) -> jsx_parser:parser(Handler, State, Config).
|
|
|
|
|
|
|
|
-opaque internal_state() :: tuple().
|
|
|
|
|
2013-03-05 21:10:33 -08:00
|
|
|
-spec resume(Term::json_text() | token(), InternalState::internal_state(), Config::list()) -> any().
|
2013-03-05 20:02:52 -08:00
|
|
|
|
|
|
|
resume(Term, {decoder, State, Handler, Acc, Stack}, Config) ->
|
2013-03-06 01:37:08 -08:00
|
|
|
jsx_decoder:resume(Term, State, Handler, Acc, Stack, jsx_config:parse_config(Config));
|
2013-03-05 21:10:33 -08:00
|
|
|
resume(Term, {parser, State, Handler, Stack}, Config) ->
|
2013-09-02 08:54:10 +01:00
|
|
|
jsx_parser:resume(Term, State, Handler, Stack, jsx_config:parse_config(Config)).
|
2013-10-20 21:06:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-ifdef(TEST).
|
|
|
|
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
|
|
|
|
|
|
|
|
%% test handler
|
|
|
|
init([]) -> [].
|
|
|
|
|
|
|
|
handle_event(end_json, State) -> lists:reverse([end_json] ++ State);
|
|
|
|
handle_event(Event, State) -> [Event] ++ State.
|
|
|
|
|
|
|
|
|
|
|
|
test_cases() ->
|
|
|
|
empty_array()
|
|
|
|
++ nested_array()
|
|
|
|
++ empty_object()
|
|
|
|
++ nested_object()
|
|
|
|
++ strings()
|
|
|
|
++ literals()
|
|
|
|
++ integers()
|
|
|
|
++ floats()
|
|
|
|
++ compound_object().
|
|
|
|
|
|
|
|
|
|
|
|
empty_array() -> [{"[]", <<"[]">>, [], [start_array, end_array]}].
|
|
|
|
|
|
|
|
nested_array() ->
|
|
|
|
[{
|
|
|
|
"[[[]]]",
|
|
|
|
<<"[[[]]]">>,
|
|
|
|
[[[]]],
|
|
|
|
[start_array, start_array, start_array, end_array, end_array, end_array]
|
|
|
|
}].
|
|
|
|
|
|
|
|
|
|
|
|
empty_object() -> [{"{}", <<"{}">>, [{}], [start_object, end_object]}].
|
|
|
|
|
|
|
|
nested_object() ->
|
|
|
|
[{
|
|
|
|
"{\"key\":{\"key\":{}}}",
|
|
|
|
<<"{\"key\":{\"key\":{}}}">>,
|
|
|
|
[{<<"key">>, [{<<"key">>, [{}]}]}],
|
|
|
|
[
|
|
|
|
start_object,
|
|
|
|
{key, <<"key">>},
|
|
|
|
start_object,
|
|
|
|
{key, <<"key">>},
|
|
|
|
start_object,
|
|
|
|
end_object,
|
|
|
|
end_object,
|
|
|
|
end_object
|
|
|
|
]
|
|
|
|
}].
|
|
|
|
|
|
|
|
|
|
|
|
naked_strings() ->
|
|
|
|
Raw = [
|
|
|
|
"",
|
|
|
|
"hello world"
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
String,
|
|
|
|
<<"\"", (list_to_binary(String))/binary, "\"">>,
|
|
|
|
list_to_binary(String),
|
|
|
|
[{string, list_to_binary(String)}]
|
|
|
|
}
|
|
|
|
|| String <- Raw
|
|
|
|
].
|
|
|
|
|
|
|
|
strings() ->
|
|
|
|
naked_strings()
|
|
|
|
++ [ wrap_with_array(Test) || Test <- naked_strings() ]
|
|
|
|
++ [ wrap_with_object(Test) || Test <- naked_strings() ].
|
|
|
|
|
|
|
|
|
|
|
|
naked_integers() ->
|
|
|
|
Raw = [
|
|
|
|
1, 2, 3,
|
|
|
|
127, 128, 129,
|
|
|
|
255, 256, 257,
|
|
|
|
65534, 65535, 65536,
|
|
|
|
18446744073709551616,
|
|
|
|
18446744073709551617
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
integer_to_list(X),
|
|
|
|
list_to_binary(integer_to_list(X)),
|
|
|
|
X,
|
|
|
|
[{integer, X}]
|
|
|
|
}
|
|
|
|
|| X <- Raw ++ [ -1 * Y || Y <- Raw ] ++ [0]
|
|
|
|
].
|
|
|
|
|
|
|
|
integers() ->
|
|
|
|
naked_integers()
|
|
|
|
++ [ wrap_with_array(Test) || Test <- naked_integers() ]
|
|
|
|
++ [ wrap_with_object(Test) || Test <- naked_integers() ].
|
|
|
|
|
|
|
|
|
|
|
|
naked_floats() ->
|
|
|
|
Raw = [
|
|
|
|
0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9,
|
|
|
|
1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9,
|
|
|
|
1234567890.0987654321,
|
|
|
|
0.0e0,
|
|
|
|
1234567890.0987654321e16,
|
|
|
|
0.1e0, 0.1e1, 0.1e2, 0.1e4, 0.1e8, 0.1e16, 0.1e308,
|
|
|
|
1.0e0, 1.0e1, 1.0e2, 1.0e4, 1.0e8, 1.0e16, 1.0e308,
|
|
|
|
2.2250738585072014e-308, %% min normalized float
|
|
|
|
1.7976931348623157e308, %% max normalized float
|
|
|
|
5.0e-324, %% min denormalized float
|
|
|
|
2.225073858507201e-308 %% max denormalized float
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
sane_float_to_list(X),
|
|
|
|
list_to_binary(sane_float_to_list(X)),
|
|
|
|
X,
|
|
|
|
[{float, X}]
|
|
|
|
}
|
|
|
|
|| X <- Raw ++ [ -1 * Y || Y <- Raw ]
|
|
|
|
].
|
|
|
|
|
|
|
|
floats() ->
|
|
|
|
naked_floats()
|
|
|
|
++ [ wrap_with_array(Test) || Test <- naked_floats() ]
|
|
|
|
++ [ wrap_with_object(Test) || Test <- naked_floats() ].
|
|
|
|
|
|
|
|
|
|
|
|
naked_literals() ->
|
|
|
|
[
|
|
|
|
{
|
|
|
|
atom_to_list(Literal),
|
|
|
|
atom_to_binary(Literal, unicode),
|
|
|
|
Literal,
|
|
|
|
[{literal, Literal}]
|
|
|
|
}
|
|
|
|
|| Literal <- [true, false, null]
|
|
|
|
].
|
|
|
|
|
|
|
|
literals() ->
|
|
|
|
naked_literals()
|
|
|
|
++ [ wrap_with_array(Test) || Test <- naked_literals() ]
|
|
|
|
++ [ wrap_with_object(Test) || Test <- naked_literals() ].
|
|
|
|
|
|
|
|
|
|
|
|
compound_object() ->
|
|
|
|
[{
|
|
|
|
"[{\"alpha\":[1,2,3],\"beta\":{\"alpha\":[1.0,2.0,3.0],\"beta\":[true,false]}},[{}]]",
|
|
|
|
<<"[{\"alpha\":[1,2,3],\"beta\":{\"alpha\":[1.0,2.0,3.0],\"beta\":[true,false]}},[{}]]">>,
|
|
|
|
[[{<<"alpha">>, [1, 2, 3]}, {<<"beta">>, [{<<"alpha">>, [1.0, 2.0, 3.0]}, {<<"beta">>, [true, false]}]}], [[{}]]],
|
|
|
|
[
|
|
|
|
start_array,
|
|
|
|
start_object,
|
|
|
|
{key, <<"alpha">>},
|
|
|
|
start_array,
|
|
|
|
{integer, 1},
|
|
|
|
{integer, 2},
|
|
|
|
{integer, 3},
|
|
|
|
end_array,
|
|
|
|
{key, <<"beta">>},
|
|
|
|
start_object,
|
|
|
|
{key, <<"alpha">>},
|
|
|
|
start_array,
|
|
|
|
{float, 1.0},
|
|
|
|
{float, 2.0},
|
|
|
|
{float, 3.0},
|
|
|
|
end_array,
|
|
|
|
{key, <<"beta">>},
|
|
|
|
start_array,
|
|
|
|
{literal, true},
|
|
|
|
{literal, false},
|
|
|
|
end_array,
|
|
|
|
end_object,
|
|
|
|
end_object,
|
|
|
|
start_array,
|
|
|
|
start_object,
|
|
|
|
end_object,
|
|
|
|
end_array,
|
|
|
|
end_array
|
|
|
|
]
|
|
|
|
}].
|
|
|
|
|
|
|
|
|
|
|
|
wrap_with_array({Title, JSON, Term, Events}) ->
|
|
|
|
{
|
|
|
|
"[" ++ Title ++ "]",
|
|
|
|
<<"[", JSON/binary, "]">>,
|
|
|
|
[Term],
|
|
|
|
[start_array] ++ Events ++ [end_array]
|
|
|
|
}.
|
|
|
|
|
|
|
|
|
|
|
|
wrap_with_object({Title, JSON, Term, Events}) ->
|
|
|
|
{
|
|
|
|
"{\"key\":" ++ Title ++ "}",
|
|
|
|
<<"{\"key\":", JSON/binary, "}">>,
|
|
|
|
[{<<"key">>, Term}],
|
|
|
|
[start_object, {key, <<"key">>}] ++ Events ++ [end_object]
|
|
|
|
}.
|
|
|
|
|
|
|
|
|
|
|
|
sane_float_to_list(X) ->
|
|
|
|
[Output] = io_lib:format("~p", [X]),
|
|
|
|
Output.
|
|
|
|
|
|
|
|
|
|
|
|
incremental_decode(JSON) ->
|
|
|
|
Final = lists:foldl(
|
|
|
|
fun(Byte, Decoder) -> {incomplete, F} = Decoder(Byte), F end,
|
|
|
|
decoder(jsx, [], [stream]),
|
|
|
|
json_to_bytes(JSON)
|
|
|
|
),
|
|
|
|
Final(end_stream).
|
|
|
|
|
|
|
|
|
|
|
|
incremental_parse(Events) ->
|
|
|
|
Final = lists:foldl(
|
|
|
|
fun(Event, Parser) -> {incomplete, F} = Parser(Event), F end,
|
|
|
|
parser(?MODULE, [], [stream]),
|
|
|
|
lists:map(fun(X) -> [X] end, Events)
|
|
|
|
),
|
|
|
|
Final(end_stream).
|
|
|
|
|
|
|
|
|
|
|
|
%% used to convert a json text into a list of codepoints to be incrementally
|
|
|
|
%% parsed
|
|
|
|
json_to_bytes(JSON) -> json_to_bytes(JSON, []).
|
|
|
|
|
|
|
|
json_to_bytes(<<>>, Acc) -> [<<>>] ++ lists:reverse(Acc);
|
|
|
|
json_to_bytes(<<X, Rest/binary>>, Acc) -> json_to_bytes(Rest, [<<X>>] ++ Acc).
|
|
|
|
|
|
|
|
|
|
|
|
%% actual tests!
|
|
|
|
decode_test_() ->
|
|
|
|
Data = test_cases(),
|
|
|
|
[{Title, ?_assertEqual(Events ++ [end_json], (decoder(?MODULE, [], []))(JSON))}
|
|
|
|
|| {Title, JSON, _, Events} <- Data
|
|
|
|
] ++
|
|
|
|
[{Title ++ " (incremental)", ?_assertEqual(Events ++ [end_json], incremental_decode(JSON))}
|
|
|
|
|| {Title, JSON, _, Events} <- Data
|
|
|
|
].
|
|
|
|
|
|
|
|
|
|
|
|
parse_test_() ->
|
|
|
|
Data = test_cases(),
|
|
|
|
[{Title, ?_assertEqual(Events ++ [end_json], (parser(?MODULE, [], []))(Events ++ [end_json]))}
|
|
|
|
|| {Title, _, _, Events} <- Data
|
|
|
|
] ++
|
|
|
|
[{Title ++ " (incremental)", ?_assertEqual(Events ++ [end_json], incremental_parse(Events))}
|
|
|
|
|| {Title, _, _, Events} <- Data
|
|
|
|
].
|
|
|
|
|
|
|
|
|
|
|
|
encode_test_() ->
|
|
|
|
Data = test_cases(),
|
|
|
|
[
|
|
|
|
{
|
|
|
|
Title, ?_assertEqual(
|
|
|
|
Events ++ [end_json],
|
|
|
|
(jsx:encoder(jsx, [], []))(Term)
|
|
|
|
)
|
|
|
|
} || {Title, _, Term, Events} <- Data
|
|
|
|
].
|
|
|
|
|
|
|
|
|
|
|
|
-endif.
|