v1.1
This commit is contained in:
commit
bd6202e618
43 changed files with 1473 additions and 628 deletions
|
@ -1,7 +1,7 @@
|
|||
{application, jsx,
|
||||
[
|
||||
{description, "a streaming, evented json parsing toolkit"},
|
||||
{vsn, "1.0.2"},
|
||||
{vsn, "1.1"},
|
||||
{modules, [
|
||||
jsx,
|
||||
jsx_encoder,
|
||||
|
|
55
src/jsx.erl
55
src/jsx.erl
|
@ -77,7 +77,6 @@ format(Source, Opts) -> jsx_to_json:format(Source, Opts).
|
|||
| float()
|
||||
| binary().
|
||||
|
||||
|
||||
to_term(Source) -> to_term(Source, []).
|
||||
|
||||
to_term(Source, Opts) -> jsx_to_term:to_term(Source, Opts).
|
||||
|
@ -135,6 +134,58 @@ encoder_decoder_equiv_test_() ->
|
|||
].
|
||||
|
||||
|
||||
single_quotes_test_() ->
|
||||
[
|
||||
{"single quoted keys",
|
||||
?_assertEqual(
|
||||
to_term(<<"{'key':true}">>, [single_quotes]),
|
||||
[{<<"key">>, true}]
|
||||
)
|
||||
},
|
||||
{"multiple single quoted keys",
|
||||
?_assertEqual(
|
||||
to_term(<<"{'key':true, 'another key':true}">>, [single_quotes]),
|
||||
[{<<"key">>, true}, {<<"another key">>, true}]
|
||||
)
|
||||
},
|
||||
{"nested single quoted keys",
|
||||
?_assertEqual(
|
||||
to_term(<<"{'key': {'key':true, 'another key':true}}">>, [single_quotes]),
|
||||
[{<<"key">>, [{<<"key">>, true}, {<<"another key">>, true}]}]
|
||||
)
|
||||
},
|
||||
{"single quoted string",
|
||||
?_assertEqual(
|
||||
to_term(<<"['string']">>, [single_quotes]),
|
||||
[<<"string">>]
|
||||
)
|
||||
},
|
||||
{"single quote in double quoted string",
|
||||
?_assertEqual(
|
||||
to_term(<<"[\"a single quote: '\"]">>, [single_quotes]),
|
||||
[<<"a single quote: '">>]
|
||||
)
|
||||
},
|
||||
{"escaped single quote in single quoted string",
|
||||
?_assertEqual(
|
||||
to_term(<<"['a single quote: \\'']">>, [single_quotes]),
|
||||
[<<"a single quote: '">>]
|
||||
)
|
||||
},
|
||||
{"escaped single quote when single quotes are disallowed",
|
||||
?_assertError(
|
||||
badarg,
|
||||
to_term(<<"[\"a single quote: \\'\"]">>)
|
||||
)
|
||||
},
|
||||
{"mismatched quotes",
|
||||
?_assertError(
|
||||
badarg,
|
||||
to_term(<<"['mismatched\"]">>, [single_quotes])
|
||||
)
|
||||
}
|
||||
].
|
||||
|
||||
|
||||
%% test handler
|
||||
init([]) -> [].
|
||||
|
@ -209,7 +260,7 @@ decode(JSON, Flags) ->
|
|||
incremental_decode(<<C:1/binary, Rest/binary>>, Flags) ->
|
||||
P = jsx_decoder:decoder(?MODULE, [], Flags ++ [explicit_end]),
|
||||
try incremental_decode_loop(P(C), Rest)
|
||||
catch error:badarg -> io:format("~p~n", [erlang:get_stacktrace()]), {error, badjson}
|
||||
catch error:badarg -> {error, badjson}
|
||||
end.
|
||||
|
||||
incremental_decode_loop({incomplete, More}, <<>>) ->
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,7 +25,6 @@
|
|||
|
||||
-export([encoder/3]).
|
||||
|
||||
|
||||
-spec encoder(Handler::module(), State::any(), Opts::jsx:opts()) -> jsx:encoder().
|
||||
|
||||
encoder(Handler, State, Opts) ->
|
||||
|
@ -53,8 +52,8 @@ start(Term, {Handler, State}, Opts) ->
|
|||
Handler:handle_event(end_json, value(Term, {Handler, State}, Opts)).
|
||||
|
||||
|
||||
value(String, {Handler, State}, _Opts) when is_binary(String) ->
|
||||
Handler:handle_event({string, String}, State);
|
||||
value(String, {Handler, State}, Opts) when is_binary(String) ->
|
||||
Handler:handle_event({string, clean_string(String, Opts)}, State);
|
||||
value(Float, {Handler, State}, _Opts) when is_float(Float) ->
|
||||
Handler:handle_event({float, Float}, State);
|
||||
value(Int, {Handler, State}, _Opts) when is_integer(Int) ->
|
||||
|
@ -78,9 +77,18 @@ list_or_object(List, {Handler, State}, Opts) ->
|
|||
|
||||
|
||||
object([{Key, Value}|Rest], {Handler, State}, Opts) ->
|
||||
object(Rest, {Handler,
|
||||
value(Value, {Handler, Handler:handle_event({key, fix_key(Key)}, State)}, Opts)
|
||||
}, Opts);
|
||||
object(
|
||||
Rest,
|
||||
{
|
||||
Handler,
|
||||
value(
|
||||
Value,
|
||||
{Handler, Handler:handle_event({key, clean_string(fix_key(Key), Opts)}, State)},
|
||||
Opts
|
||||
)
|
||||
},
|
||||
Opts
|
||||
);
|
||||
object([], {Handler, State}, _Opts) -> Handler:handle_event(end_object, State);
|
||||
object(Term, Handler, Opts) -> ?error([Term, Handler, Opts]).
|
||||
|
||||
|
@ -91,8 +99,33 @@ list([], {Handler, State}, _Opts) -> Handler:handle_event(end_array, State);
|
|||
list(Term, Handler, Opts) -> ?error([Term, Handler, Opts]).
|
||||
|
||||
|
||||
fix_key(Key) when is_binary(Key) -> Key;
|
||||
fix_key(Key) when is_atom(Key) -> atom_to_binary(Key, utf8).
|
||||
fix_key(Key) when is_atom(Key) -> fix_key(atom_to_binary(Key, utf8));
|
||||
fix_key(Key) when is_binary(Key) -> Key.
|
||||
|
||||
|
||||
clean_string(Bin, Opts) ->
|
||||
case Opts#opts.json_escape of
|
||||
true -> jsx_utils:json_escape(Bin, Opts);
|
||||
false ->
|
||||
case is_clean(Bin) of
|
||||
true -> Bin;
|
||||
false -> clean_string(Bin, [], Opts)
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
is_clean(<<>>) -> true;
|
||||
is_clean(<<_/utf8, Rest/binary>>) -> is_clean(Rest);
|
||||
is_clean(_) -> false.
|
||||
|
||||
|
||||
clean_string(Bin, _Acc, Opts=#opts{loose_unicode=false}) -> ?error([Bin, Opts]);
|
||||
clean_string(<<>>, Acc, _Opts) -> unicode:characters_to_binary(lists:reverse(Acc));
|
||||
clean_string(<<X/utf8, Rest/binary>>, Acc, Opts) -> clean_string(Rest, [X] ++ Acc, Opts);
|
||||
%% surrogates
|
||||
clean_string(<<237, X, _, Rest/binary>>, Acc, Opts) when X >= 160 -> clean_string(Rest, [16#fffd] ++ Acc, Opts);
|
||||
%% bad codepoints
|
||||
clean_string(<<_, Rest/binary>>, Acc, Opts) -> clean_string(Rest, [16#fffd] ++ Acc, Opts).
|
||||
|
||||
|
||||
-ifdef(TEST).
|
||||
|
@ -100,52 +133,56 @@ fix_key(Key) when is_atom(Key) -> atom_to_binary(Key, utf8).
|
|||
|
||||
encode(Term) -> (encoder(jsx, [], []))(Term).
|
||||
|
||||
encode(Term, Opts) ->
|
||||
try (encoder(jsx, [], Opts))(Term)
|
||||
catch _:_ -> {error, badjson}
|
||||
end.
|
||||
|
||||
|
||||
encode_test_() ->
|
||||
[
|
||||
{"naked string", ?_assert(encode(<<"a string">>)
|
||||
=:= [{string, <<"a string">>}, end_json])
|
||||
},
|
||||
{"naked integer", ?_assert(encode(123)
|
||||
=:= [{integer, 123}, end_json])
|
||||
},
|
||||
{"naked float", ?_assert(encode(1.23)
|
||||
=:= [{float, 1.23}, end_json])
|
||||
},
|
||||
{"naked literal", ?_assert(encode(null)
|
||||
=:= [{literal, null}, end_json])
|
||||
},
|
||||
{"empty object", ?_assert(encode([{}])
|
||||
=:= [start_object, end_object, end_json])
|
||||
},
|
||||
{"empty list", ?_assert(encode([])
|
||||
=:= [start_array, end_array, end_json])
|
||||
},
|
||||
{"simple list", ?_assert(encode([1,2,3,true,false])
|
||||
=:= [start_array,
|
||||
{"naked string", ?_assertEqual(encode(<<"a string">>), [{string, <<"a string">>}, end_json])},
|
||||
{"naked integer", ?_assertEqual(encode(123), [{integer, 123}, end_json])},
|
||||
{"naked float", ?_assertEqual(encode(1.23), [{float, 1.23}, end_json])},
|
||||
{"naked literal", ?_assertEqual(encode(null), [{literal, null}, end_json])},
|
||||
{"empty object", ?_assertEqual(encode([{}]), [start_object, end_object, end_json])},
|
||||
{"empty list", ?_assertEqual(encode([]), [start_array, end_array, end_json])},
|
||||
{"simple list", ?_assertEqual(
|
||||
encode([1,2,3,true,false]),
|
||||
[
|
||||
start_array,
|
||||
{integer, 1},
|
||||
{integer, 2},
|
||||
{integer, 3},
|
||||
{literal, true},
|
||||
{literal, false},
|
||||
end_array,
|
||||
end_json])
|
||||
end_json
|
||||
]
|
||||
)
|
||||
},
|
||||
{"simple object", ?_assert(encode([{<<"a">>, true}, {<<"b">>, false}])
|
||||
=:= [start_object,
|
||||
{"simple object", ?_assertEqual(
|
||||
encode([{<<"a">>, true}, {<<"b">>, false}]),
|
||||
[
|
||||
start_object,
|
||||
{key, <<"a">>},
|
||||
{literal, true},
|
||||
{key, <<"b">>},
|
||||
{literal, false},
|
||||
end_object,
|
||||
end_json])
|
||||
end_json
|
||||
]
|
||||
)
|
||||
},
|
||||
{"complex term", ?_assert(encode([
|
||||
{<<"a">>, true},
|
||||
{<<"b">>, false},
|
||||
{<<"c">>, [1,2,3]},
|
||||
{<<"d">>, [{<<"key">>, <<"value">>}]}
|
||||
]) =:= [start_object,
|
||||
{"complex term", ?_assertEqual(
|
||||
encode([
|
||||
{<<"a">>, true},
|
||||
{<<"b">>, false},
|
||||
{<<"c">>, [1,2,3]},
|
||||
{<<"d">>, [{<<"key">>, <<"value">>}]}
|
||||
]),
|
||||
[
|
||||
start_object,
|
||||
{key, <<"a">>},
|
||||
{literal, true},
|
||||
{key, <<"b">>},
|
||||
|
@ -162,15 +199,113 @@ encode_test_() ->
|
|||
{string, <<"value">>},
|
||||
end_object,
|
||||
end_object,
|
||||
end_json])
|
||||
end_json
|
||||
]
|
||||
)
|
||||
},
|
||||
{"atom keys", ?_assert(encode([{key, <<"value">>}])
|
||||
=:= [start_object,
|
||||
{key, <<"key">>},
|
||||
{string, <<"value">>},
|
||||
end_object,
|
||||
end_json])
|
||||
{"atom keys", ?_assertEqual(
|
||||
encode([{key, <<"value">>}]),
|
||||
[start_object, {key, <<"key">>}, {string, <<"value">>}, end_object, end_json]
|
||||
)
|
||||
}
|
||||
].
|
||||
|
||||
surrogates_test_() ->
|
||||
[
|
||||
{"surrogates - badjson",
|
||||
?_assertEqual(check_bad(surrogates()), [])
|
||||
},
|
||||
{"surrogates - replaced",
|
||||
?_assertEqual(check_replaced(surrogates()), [])
|
||||
}
|
||||
].
|
||||
|
||||
good_characters_test_() ->
|
||||
[
|
||||
{"acceptable codepoints",
|
||||
?_assertEqual(check_good(good()), [])
|
||||
},
|
||||
{"acceptable extended",
|
||||
?_assertEqual(check_good(good_extended()), [])
|
||||
}
|
||||
].
|
||||
|
||||
malformed_test_() ->
|
||||
[
|
||||
{"malformed codepoint with 1 byte", ?_assertError(badarg, encode(<<128>>))},
|
||||
{"malformed codepoint with 2 bytes", ?_assertError(badarg, encode(<<128, 192>>))},
|
||||
{"malformed codepoint with 3 bytes", ?_assertError(badarg, encode(<<128, 192, 192>>))},
|
||||
{"malformed codepoint with 4 bytes", ?_assertError(badarg, encode(<<128, 192, 192, 192>>))}
|
||||
].
|
||||
|
||||
malformed_replaced_test_() ->
|
||||
F = <<16#fffd/utf8>>,
|
||||
[
|
||||
{"malformed codepoint with 1 byte",
|
||||
?_assertEqual(
|
||||
[{string, <<F/binary>>}, end_json],
|
||||
encode(<<128>>, [loose_unicode])
|
||||
)
|
||||
},
|
||||
{"malformed codepoint with 2 bytes",
|
||||
?_assertEqual(
|
||||
[{string, <<F/binary, F/binary>>}, end_json],
|
||||
encode(<<128, 192>>, [loose_unicode])
|
||||
)
|
||||
},
|
||||
{"malformed codepoint with 3 bytes",
|
||||
?_assertEqual(
|
||||
[{string, <<F/binary, F/binary, F/binary>>}, end_json],
|
||||
encode(<<128, 192, 192>>, [loose_unicode])
|
||||
)
|
||||
},
|
||||
{"malformed codepoint with 4 bytes",
|
||||
?_assertEqual(
|
||||
[{string, <<F/binary, F/binary, F/binary, F/binary>>}, end_json],
|
||||
encode(<<128, 192, 192, 192>>, [loose_unicode])
|
||||
)
|
||||
}
|
||||
].
|
||||
|
||||
check_bad(List) ->
|
||||
lists:dropwhile(fun({_, {error, badjson}}) -> true ; (_) -> false end,
|
||||
check(List, [], [])
|
||||
).
|
||||
|
||||
check_replaced(List) ->
|
||||
lists:dropwhile(fun({_, [{string, <<16#fffd/utf8>>}|_]}) -> true
|
||||
; (_) -> false
|
||||
end,
|
||||
check(List, [loose_unicode], [])
|
||||
).
|
||||
|
||||
check_good(List) ->
|
||||
lists:dropwhile(fun({_, [{string, _}|_]}) -> true ; (_) -> false end,
|
||||
check(List, [], [])
|
||||
).
|
||||
|
||||
check([], _Opts, Acc) -> Acc;
|
||||
check([H|T], Opts, Acc) ->
|
||||
R = encode(to_fake_utf(H, utf8), Opts),
|
||||
check(T, Opts, [{H, R}] ++ Acc).
|
||||
|
||||
|
||||
surrogates() -> lists:seq(16#d800, 16#dfff).
|
||||
|
||||
good() -> lists:seq(1, 16#d7ff) ++ lists:seq(16#e000, 16#ffff).
|
||||
|
||||
good_extended() -> lists:seq(16#100000, 16#10ffff).
|
||||
|
||||
%% erlang refuses to encode certain codepoints, so fake them all
|
||||
to_fake_utf(N, utf8) when N < 16#0080 -> <<N:8>>;
|
||||
to_fake_utf(N, utf8) when N < 16#0800 ->
|
||||
<<0:5, Y:5, X:6>> = <<N:16>>,
|
||||
<<2#110:3, Y:5, 2#10:2, X:6>>;
|
||||
to_fake_utf(N, utf8) when N < 16#10000 ->
|
||||
<<Z:4, Y:6, X:6>> = <<N:16>>,
|
||||
<<2#1110:4, Z:4, 2#10:2, Y:6, 2#10:2, X:6>>;
|
||||
to_fake_utf(N, utf8) ->
|
||||
<<0:3, W:3, Z:6, Y:6, X:6>> = <<N:24>>,
|
||||
<<2#11110:5, W:3, 2#10:2, Z:6, 2#10:2, Y:6, 2#10:2, X:6>>.
|
||||
|
||||
-endif.
|
|
@ -2,5 +2,8 @@
|
|||
loose_unicode = false,
|
||||
escape_forward_slash = false,
|
||||
explicit_end = false,
|
||||
parser = auto
|
||||
single_quotes = false,
|
||||
no_jsonp_escapes = false,
|
||||
comments = false,
|
||||
json_escape = false
|
||||
}).
|
|
@ -39,7 +39,7 @@
|
|||
-spec to_json(Source::any(), Opts::opts()) -> binary().
|
||||
|
||||
to_json(Source, Opts) when is_list(Opts) ->
|
||||
(jsx:encoder(?MODULE, Opts, jsx_utils:extract_opts(Opts)))(Source).
|
||||
(jsx:encoder(?MODULE, Opts, jsx_utils:extract_opts([json_escape] ++ Opts)))(Source).
|
||||
|
||||
|
||||
-spec format(Source::binary(), Opts::opts()) -> binary().
|
||||
|
@ -135,8 +135,8 @@ handle_event(Event, {[array|Stack], Acc, Opts = #opts{depth = Depth}}) ->
|
|||
handle_event(end_json, {[], Acc, _Opts}) -> unicode:characters_to_binary(Acc, utf8).
|
||||
|
||||
|
||||
encode(string, String, Opts) ->
|
||||
[?quote, jsx_utils:json_escape(String, Opts), ?quote];
|
||||
encode(string, String, _Opts) ->
|
||||
[?quote, String, ?quote];
|
||||
encode(literal, Literal, _Opts) ->
|
||||
erlang:atom_to_list(Literal);
|
||||
encode(integer, Integer, _Opts) ->
|
||||
|
@ -186,176 +186,148 @@ teardown_nicedecimal_meck(_) ->
|
|||
|
||||
basic_format_test_() ->
|
||||
[
|
||||
{"empty object", ?_assert(format(<<"{}">>, []) =:= <<"{}">>)},
|
||||
{"empty array", ?_assert(format(<<"[]">>, []) =:= <<"[]">>)},
|
||||
{"naked integer", ?_assert(format(<<"123">>, []) =:= <<"123">>)},
|
||||
{"empty object", ?_assertEqual(format(<<"{}">>, []), <<"{}">>)},
|
||||
{"empty array", ?_assertEqual(format(<<"[]">>, []), <<"[]">>)},
|
||||
{"naked integer", ?_assertEqual(format(<<"123">>, []), <<"123">>)},
|
||||
{foreach,
|
||||
fun() -> setup_nicedecimal_meck(<<"1.23">>) end,
|
||||
fun(R) -> teardown_nicedecimal_meck(R) end,
|
||||
[{"naked float", ?_assert(format(<<"1.23">>, []) =:= <<"1.23">>)}]
|
||||
},
|
||||
{"naked string", ?_assert(format(<<"\"hi\"">>, []) =:= <<"\"hi\"">>)},
|
||||
{"naked literal", ?_assert(format(<<"true">>, []) =:= <<"true">>)},
|
||||
{"simple object",
|
||||
?_assert(format(<<" { \"key\" :\n\t \"value\"\r\r\r\n } ">>,
|
||||
[]
|
||||
) =:= <<"{\"key\":\"value\"}">>
|
||||
)
|
||||
},
|
||||
{"really simple object",
|
||||
?_assert(format(<<"{\"k\":\"v\"}">>, []) =:= <<"{\"k\":\"v\"}">>)
|
||||
},
|
||||
{"nested object",
|
||||
?_assert(format(<<"{\"k\":{\"k\":\"v\"}, \"j\":{}}">>, []
|
||||
) =:= <<"{\"k\":{\"k\":\"v\"},\"j\":{}}">>
|
||||
)
|
||||
},
|
||||
{"simple array",
|
||||
?_assert(format(<<" [\n\ttrue,\n\tfalse , \n \tnull\n] ">>,
|
||||
[]
|
||||
) =:= <<"[true,false,null]">>
|
||||
)
|
||||
},
|
||||
{"really simple array", ?_assert(format(<<"[1]">>, []) =:= <<"[1]">>)},
|
||||
{"nested array", ?_assert(format(<<"[[[]]]">>, []) =:= <<"[[[]]]">>)},
|
||||
{"nested structures",
|
||||
?_assert(format(
|
||||
<<"[{\"key\":\"value\",
|
||||
\"another key\": \"another value\",
|
||||
\"a list\": [true, false]
|
||||
},
|
||||
[[{}]]
|
||||
]">>, []
|
||||
) =:= <<"[{\"key\":\"value\",\"another key\":\"another value\",\"a list\":[true,false]},[[{}]]]">>
|
||||
)
|
||||
[{"naked float", ?_assertEqual(format(<<"1.23">>, []), <<"1.23">>)}]
|
||||
},
|
||||
{"naked string", ?_assertEqual(format(<<"\"hi\"">>, []), <<"\"hi\"">>)},
|
||||
{"naked literal", ?_assertEqual(format(<<"true">>, []), <<"true">>)},
|
||||
{"simple object", ?_assertEqual(
|
||||
format(<<" { \"key\" :\n\t \"value\"\r\r\r\n } ">>, []),
|
||||
<<"{\"key\":\"value\"}">>
|
||||
)},
|
||||
{"really simple object", ?_assertEqual(format(<<"{\"k\":\"v\"}">>, []) , <<"{\"k\":\"v\"}">>)},
|
||||
{"nested object", ?_assertEqual(
|
||||
format(<<"{\"k\":{\"k\":\"v\"}, \"j\":{}}">>, []),
|
||||
<<"{\"k\":{\"k\":\"v\"},\"j\":{}}">>
|
||||
)},
|
||||
{"simple array", ?_assertEqual(
|
||||
format(<<" [\n\ttrue,\n\tfalse , \n \tnull\n] ">>, []),
|
||||
<<"[true,false,null]">>
|
||||
)},
|
||||
{"really simple array", ?_assertEqual(format(<<"[1]">>, []), <<"[1]">>)},
|
||||
{"nested array", ?_assertEqual(format(<<"[[[]]]">>, []), <<"[[[]]]">>)},
|
||||
{"nested structures", ?_assertEqual(
|
||||
format(<<"[
|
||||
{
|
||||
\"key\":\"value\",
|
||||
\"another key\": \"another value\",
|
||||
\"a list\": [true, false]
|
||||
},
|
||||
[[{}]]
|
||||
]">>, []),
|
||||
<<"[{\"key\":\"value\",\"another key\":\"another value\",\"a list\":[true,false]},[[{}]]]">>
|
||||
)},
|
||||
{"simple nested structure",
|
||||
?_assert(format(<<"[[],{\"k\":[[],{}],\"j\":{}},[]]">>, []
|
||||
) =:= <<"[[],{\"k\":[[],{}],\"j\":{}},[]]">>
|
||||
?_assertEqual(
|
||||
format(<<"[[],{\"k\":[[],{}],\"j\":{}},[]]">>, []),
|
||||
<<"[[],{\"k\":[[],{}],\"j\":{}},[]]">>
|
||||
)
|
||||
}
|
||||
].
|
||||
|
||||
basic_to_json_test_() ->
|
||||
[
|
||||
{"empty object", ?_assert(to_json([{}], []) =:= <<"{}">>)},
|
||||
{"empty array", ?_assert(to_json([], []) =:= <<"[]">>)},
|
||||
{"naked integer", ?_assert(to_json(123, []) =:= <<"123">>)},
|
||||
{"empty object", ?_assertEqual(to_json([{}], []), <<"{}">>)},
|
||||
{"empty array", ?_assertEqual(to_json([], []), <<"[]">>)},
|
||||
{"naked integer", ?_assertEqual(to_json(123, []), <<"123">>)},
|
||||
{foreach,
|
||||
fun() -> setup_nicedecimal_meck(<<"1.23">>) end,
|
||||
fun(R) -> teardown_nicedecimal_meck(R) end,
|
||||
[{"naked float", ?_assert(to_json(1.23, []) =:= <<"1.23">>)}]
|
||||
[{"naked float", ?_assertEqual(to_json(1.23, []) , <<"1.23">>)}]
|
||||
},
|
||||
{"naked string", ?_assert(to_json(<<"hi">>, []) =:= <<"\"hi\"">>)},
|
||||
{"naked literal", ?_assert(to_json(true, []) =:= <<"true">>)},
|
||||
{"simple object",
|
||||
?_assert(to_json(
|
||||
[{<<"key">>, <<"value">>}],
|
||||
[]
|
||||
) =:= <<"{\"key\":\"value\"}">>
|
||||
)
|
||||
},
|
||||
{"nested object",
|
||||
?_assert(to_json(
|
||||
[{<<"k">>,[{<<"k">>,<<"v">>}]},{<<"j">>,[{}]}],
|
||||
[]
|
||||
) =:= <<"{\"k\":{\"k\":\"v\"},\"j\":{}}">>
|
||||
)
|
||||
},
|
||||
{"simple array",
|
||||
?_assert(to_json(
|
||||
[true, false, null],
|
||||
[]
|
||||
) =:= <<"[true,false,null]">>
|
||||
)
|
||||
},
|
||||
{"really simple array", ?_assert(to_json([1], []) =:= <<"[1]">>)},
|
||||
{"nested array", ?_assert(to_json([[[]]], []) =:= <<"[[[]]]">>)},
|
||||
{"nested structures",
|
||||
?_assert(to_json(
|
||||
{"naked string", ?_assertEqual(to_json(<<"hi">>, []), <<"\"hi\"">>)},
|
||||
{"naked literal", ?_assertEqual(to_json(true, []), <<"true">>)},
|
||||
{"simple object", ?_assertEqual(
|
||||
to_json(
|
||||
[{<<"key">>, <<"value">>}],
|
||||
[]
|
||||
),
|
||||
<<"{\"key\":\"value\"}">>
|
||||
)},
|
||||
{"nested object", ?_assertEqual(
|
||||
to_json(
|
||||
[{<<"k">>,[{<<"k">>,<<"v">>}]},{<<"j">>,[{}]}],
|
||||
[]
|
||||
),
|
||||
<<"{\"k\":{\"k\":\"v\"},\"j\":{}}">>
|
||||
)},
|
||||
{"simple array", ?_assertEqual(to_json([true, false, null], []), <<"[true,false,null]">>)},
|
||||
{"really simple array", ?_assertEqual(to_json([1], []), <<"[1]">>)},
|
||||
{"nested array", ?_assertEqual(to_json([[[]]], []), <<"[[[]]]">>)},
|
||||
{"nested structures", ?_assertEqual(
|
||||
to_json(
|
||||
[
|
||||
[
|
||||
[
|
||||
{<<"key">>, <<"value">>},
|
||||
{<<"another key">>, <<"another value">>},
|
||||
{<<"a list">>, [true, false]}
|
||||
],
|
||||
[[[{}]]]
|
||||
{<<"key">>, <<"value">>},
|
||||
{<<"another key">>, <<"another value">>},
|
||||
{<<"a list">>, [true, false]}
|
||||
],
|
||||
[]
|
||||
) =:= <<"[{\"key\":\"value\",\"another key\":\"another value\",\"a list\":[true,false]},[[{}]]]">>
|
||||
)
|
||||
},
|
||||
{"simple nested structure",
|
||||
?_assert(to_json(
|
||||
[[], [{<<"k">>, [[], [{}]]}, {<<"j">>, [{}]}], []],
|
||||
[]
|
||||
) =:= <<"[[],{\"k\":[[],{}],\"j\":{}},[]]">>
|
||||
)
|
||||
}
|
||||
[[[{}]]]
|
||||
],
|
||||
[]
|
||||
),
|
||||
<<"[{\"key\":\"value\",\"another key\":\"another value\",\"a list\":[true,false]},[[{}]]]">>
|
||||
)},
|
||||
{"simple nested structure", ?_assertEqual(
|
||||
to_json(
|
||||
[[], [{<<"k">>, [[], [{}]]}, {<<"j">>, [{}]}], []],
|
||||
[]
|
||||
),
|
||||
<<"[[],{\"k\":[[],{}],\"j\":{}},[]]">>
|
||||
)}
|
||||
].
|
||||
|
||||
opts_test_() ->
|
||||
[
|
||||
{"unspecified indent/space",
|
||||
?_assert(format(<<" [\n\ttrue,\n\tfalse,\n\tnull\n] ">>,
|
||||
[space, indent]
|
||||
) =:= <<"[\n true,\n false,\n null\n]">>
|
||||
)
|
||||
},
|
||||
{"specific indent/space",
|
||||
?_assert(format(
|
||||
<<"\n{\n\"key\" : [],\n\"another key\" : true\n}\n">>,
|
||||
[{space, 2}, {indent, 3}]
|
||||
) =:= <<"{\n \"key\": [],\n \"another key\": true\n}">>
|
||||
)
|
||||
},
|
||||
{"nested structures",
|
||||
?_assert(format(
|
||||
<<"[{\"key\":\"value\",
|
||||
\"another key\": \"another value\"
|
||||
},
|
||||
[[true, false, null]]
|
||||
]">>,
|
||||
[{space, 2}, {indent, 2}]
|
||||
) =:= <<"[\n {\n \"key\": \"value\",\n \"another key\": \"another value\"\n },\n [\n [\n true,\n false,\n null\n ]\n ]\n]">>
|
||||
)
|
||||
},
|
||||
{"array spaces",
|
||||
?_assert(format(<<"[1,2,3]">>,
|
||||
[{space, 2}]
|
||||
) =:= <<"[1, 2, 3]">>
|
||||
)
|
||||
},
|
||||
{"object spaces",
|
||||
?_assert(format(<<"{\"a\":true,\"b\":true,\"c\":true}">>,
|
||||
[{space, 2}]
|
||||
) =:= <<"{\"a\": true, \"b\": true, \"c\": true}">>
|
||||
)
|
||||
},
|
||||
{"unspecified indent/space", ?_assertEqual(
|
||||
format(<<" [\n\ttrue,\n\tfalse,\n\tnull\n] ">>, [space, indent]),
|
||||
<<"[\n true,\n false,\n null\n]">>
|
||||
)},
|
||||
{"specific indent/space", ?_assertEqual(
|
||||
format(
|
||||
<<"\n{\n\"key\" : [],\n\"another key\" : true\n}\n">>,
|
||||
[{space, 2}, {indent, 3}]
|
||||
),
|
||||
<<"{\n \"key\": [],\n \"another key\": true\n}">>
|
||||
)},
|
||||
{"nested structures", ?_assertEqual(
|
||||
format(
|
||||
<<"[{\"key\":\"value\", \"another key\": \"another value\"}, [[true, false, null]]]">>,
|
||||
[{space, 2}, {indent, 2}]
|
||||
),
|
||||
<<"[\n {\n \"key\": \"value\",\n \"another key\": \"another value\"\n },\n [\n [\n true,\n false,\n null\n ]\n ]\n]">>
|
||||
)},
|
||||
{"array spaces", ?_assertEqual(
|
||||
format(<<"[1,2,3]">>, [{space, 2}]),
|
||||
<<"[1, 2, 3]">>
|
||||
)},
|
||||
{"object spaces", ?_assertEqual(
|
||||
format(<<"{\"a\":true,\"b\":true,\"c\":true}">>, [{space, 2}]),
|
||||
<<"{\"a\": true, \"b\": true, \"c\": true}">>
|
||||
)},
|
||||
{foreach,
|
||||
fun() -> setup_nicedecimal_meck(<<"1.23">>) end,
|
||||
fun(R) -> teardown_nicedecimal_meck(R) end,
|
||||
[{
|
||||
"array indent",
|
||||
?_assert(format(<<"[1.23, 1.23, 1.23]">>,
|
||||
[{indent, 2}]
|
||||
) =:= <<"[\n 1.23,\n 1.23,\n 1.23\n]">>
|
||||
)
|
||||
}]
|
||||
[{"array indent", ?_assertEqual(
|
||||
format(<<"[1.23, 1.23, 1.23]">>, [{indent, 2}]),
|
||||
<<"[\n 1.23,\n 1.23,\n 1.23\n]">>
|
||||
)}]
|
||||
},
|
||||
{"object indent",
|
||||
?_assert(format(<<"{\"a\":true,\"b\":true,\"c\":true}">>,
|
||||
[{indent, 2}]
|
||||
) =:= <<"{\n \"a\":true,\n \"b\":true,\n \"c\":true\n}">>
|
||||
)
|
||||
}
|
||||
{"object indent", ?_assertEqual(
|
||||
format(<<"{\"a\":true,\"b\":true,\"c\":true}">>, [{indent, 2}]),
|
||||
<<"{\n \"a\":true,\n \"b\":true,\n \"c\":true\n}">>
|
||||
)}
|
||||
].
|
||||
|
||||
ext_opts_test_() ->
|
||||
[{"extopts", ?_assert(format(<<"[]">>,
|
||||
[loose_unicode, {escape_forward_slash, true}]
|
||||
) =:= <<"[]">>
|
||||
)}
|
||||
].
|
||||
[{"extopts", ?_assertEqual(
|
||||
format(<<"[]">>, [loose_unicode, {escape_forward_slash, true}]),
|
||||
<<"[]">>
|
||||
)}].
|
||||
|
||||
-endif.
|
|
@ -33,15 +33,17 @@
|
|||
|
||||
-type opts() :: list().
|
||||
|
||||
|
||||
-spec to_term(Source::binary(), Opts::opts()) -> list({binary(), any()})
|
||||
| list(any())
|
||||
-type json_value() :: list({binary(), json_value()})
|
||||
| list(json_value())
|
||||
| true
|
||||
| false
|
||||
| null
|
||||
| integer()
|
||||
| float()
|
||||
| binary().
|
||||
|
||||
|
||||
-spec to_term(Source::binary(), Opts::opts()) -> json_value().
|
||||
|
||||
to_term(Source, Opts) when is_list(Opts) ->
|
||||
(jsx:decoder(?MODULE, Opts, jsx_utils:extract_opts(Opts)))(Source).
|
||||
|
@ -109,35 +111,29 @@ format_key(Key, Opts) ->
|
|||
|
||||
basic_test_() ->
|
||||
[
|
||||
{"empty object", ?_assert(to_term(<<"{}">>, []) =:= [{}])},
|
||||
{"simple object", ?_assert(to_term(<<"{\"key\": true}">>, []) =:= [{<<"key">>, true}])},
|
||||
{"less simple object",
|
||||
?_assert(to_term(<<"{\"a\": 1, \"b\": 2}">>, []) =:= [{<<"a">>, 1}, {<<"b">>, 2}])
|
||||
},
|
||||
{"nested object",
|
||||
?_assert(to_term(<<"{\"key\": {\"key\": true}}">>, []) =:= [{<<"key">>, [{<<"key">>, true}]}])
|
||||
},
|
||||
{"empty object", ?_assertEqual(to_term(<<"{}">>, []), [{}])},
|
||||
{"simple object", ?_assertEqual(to_term(<<"{\"key\": true}">>, []), [{<<"key">>, true}])},
|
||||
{"less simple object", ?_assertEqual(
|
||||
to_term(<<"{\"a\": 1, \"b\": 2}">>, []),
|
||||
[{<<"a">>, 1}, {<<"b">>, 2}]
|
||||
)},
|
||||
{"nested object", ?_assertEqual(
|
||||
to_term(<<"{\"key\": {\"key\": true}}">>, []),
|
||||
[{<<"key">>, [{<<"key">>, true}]}]
|
||||
)},
|
||||
{"empty array", ?_assert(to_term(<<"[]">>, []) =:= [])},
|
||||
{"list of lists",
|
||||
?_assert(to_term(<<"[[],[],[]]">>, []) =:= [[], [], []])
|
||||
},
|
||||
{"list of strings",
|
||||
?_assert(to_term(<<"[\"hi\", \"there\"]">>, []) =:= [<<"hi">>, <<"there">>])
|
||||
},
|
||||
{"list of numbers",
|
||||
?_assert(to_term(<<"[1, 2.0, 3e4, -5]">>, []) =:= [1, 2.0, 3.0e4, -5])
|
||||
},
|
||||
{"list of literals",
|
||||
?_assert(to_term(<<"[true,false,null]">>, []) =:= [true,false,null])
|
||||
},
|
||||
{"list of objects",
|
||||
?_assert(to_term(<<"[{}, {\"a\":1, \"b\":2}, {\"key\":[true,false]}]">>, [])
|
||||
=:= [[{}], [{<<"a">>,1},{<<"b">>,2}], [{<<"key">>,[true,false]}]])
|
||||
}
|
||||
{"list of lists", ?_assertEqual(to_term(<<"[[],[],[]]">>, []), [[], [], []])},
|
||||
{"list of strings", ?_assertEqual(to_term(<<"[\"hi\", \"there\"]">>, []), [<<"hi">>, <<"there">>])},
|
||||
{"list of numbers", ?_assertEqual(to_term(<<"[1, 2.0, 3e4, -5]">>, []), [1, 2.0, 3.0e4, -5])},
|
||||
{"list of literals", ?_assertEqual(to_term(<<"[true,false,null]">>, []), [true,false,null])},
|
||||
{"list of objects", ?_assertEqual(
|
||||
to_term(<<"[{}, {\"a\":1, \"b\":2}, {\"key\":[true,false]}]">>, []),
|
||||
[[{}], [{<<"a">>,1},{<<"b">>,2}], [{<<"key">>,[true,false]}]]
|
||||
)}
|
||||
].
|
||||
|
||||
comprehensive_test_() ->
|
||||
{"comprehensive test", ?_assert(to_term(comp_json(), []) =:= comp_term())}.
|
||||
{"comprehensive test", ?_assertEqual(to_term(comp_json(), []), comp_term())}.
|
||||
|
||||
comp_json() ->
|
||||
<<"[
|
||||
|
@ -164,7 +160,7 @@ comp_term() ->
|
|||
].
|
||||
|
||||
atom_labels_test_() ->
|
||||
{"atom labels test", ?_assert(to_term(comp_json(), [{labels, atom}]) =:= atom_term())}.
|
||||
{"atom labels test", ?_assertEqual(to_term(comp_json(), [{labels, atom}]), atom_term())}.
|
||||
|
||||
atom_term() ->
|
||||
[
|
||||
|
@ -180,10 +176,10 @@ atom_term() ->
|
|||
|
||||
naked_test_() ->
|
||||
[
|
||||
{"naked integer", ?_assert(to_term(<<"123">>, []) =:= 123)},
|
||||
{"naked float", ?_assert(to_term(<<"-4.32e-17">>, []) =:= -4.32e-17)},
|
||||
{"naked literal", ?_assert(to_term(<<"true">>, []) =:= true)},
|
||||
{"naked string", ?_assert(to_term(<<"\"string\"">>, []) =:= <<"string">>)}
|
||||
{"naked integer", ?_assertEqual(to_term(<<"123">>, []), 123)},
|
||||
{"naked float", ?_assertEqual(to_term(<<"-4.32e-17">>, []), -4.32e-17)},
|
||||
{"naked literal", ?_assertEqual(to_term(<<"true">>, []), true)},
|
||||
{"naked string", ?_assertEqual(to_term(<<"\"string\"">>, []), <<"string">>)}
|
||||
].
|
||||
|
||||
-endif.
|
||||
|
|
|
@ -43,21 +43,41 @@ parse_opts([escape_forward_slash|Rest], Opts) ->
|
|||
parse_opts(Rest, Opts#opts{escape_forward_slash=true});
|
||||
parse_opts([explicit_end|Rest], Opts) ->
|
||||
parse_opts(Rest, Opts#opts{explicit_end=true});
|
||||
parse_opts([single_quotes|Rest], Opts) ->
|
||||
parse_opts(Rest, Opts#opts{single_quotes=true});
|
||||
parse_opts([no_jsonp_escapes|Rest], Opts) ->
|
||||
parse_opts(Rest, Opts#opts{no_jsonp_escapes=true});
|
||||
parse_opts([comments|Rest], Opts) ->
|
||||
parse_opts(Rest, Opts#opts{comments=true});
|
||||
parse_opts([json_escape|Rest], Opts) ->
|
||||
parse_opts(Rest, Opts#opts{json_escape=true});
|
||||
parse_opts(_, _) ->
|
||||
{error, badarg}.
|
||||
|
||||
|
||||
valid_flags() ->
|
||||
[
|
||||
loose_unicode,
|
||||
escape_forward_slash,
|
||||
explicit_end,
|
||||
single_quotes,
|
||||
no_jsonp_escapes,
|
||||
comments,
|
||||
json_escape
|
||||
].
|
||||
|
||||
|
||||
extract_opts(Opts) ->
|
||||
extract_parser_opts(Opts, []).
|
||||
|
||||
extract_parser_opts([], Acc) -> Acc;
|
||||
extract_parser_opts([{K,V}|Rest], Acc) ->
|
||||
case lists:member(K, [loose_unicode, escape_forward_slash, explicit_end]) of
|
||||
case lists:member(K, valid_flags()) of
|
||||
true -> extract_parser_opts(Rest, [{K,V}] ++ Acc)
|
||||
; false -> extract_parser_opts(Rest, Acc)
|
||||
end;
|
||||
extract_parser_opts([K|Rest], Acc) ->
|
||||
case lists:member(K, [loose_unicode, escape_forward_slash, explicit_end]) of
|
||||
case lists:member(K, valid_flags()) of
|
||||
true -> extract_parser_opts(Rest, [K] ++ Acc)
|
||||
; false -> extract_parser_opts(Rest, Acc)
|
||||
end.
|
||||
|
@ -68,59 +88,198 @@ extract_parser_opts([K|Rest], Acc) ->
|
|||
%% everything else should be a legal json string component
|
||||
|
||||
json_escape(String, Opts) when is_binary(String) ->
|
||||
json_escape(String, Opts, <<>>).
|
||||
json_escape(String, Opts, 0, size(String)).
|
||||
|
||||
%% double quote
|
||||
json_escape(<<$\", Rest/binary>>, Opts, Acc) ->
|
||||
json_escape(Rest, Opts, <<Acc/binary, $\\, $\">>);
|
||||
%% backslash \ reverse solidus
|
||||
json_escape(<<$\\, Rest/binary>>, Opts, Acc) ->
|
||||
json_escape(Rest, Opts, <<Acc/binary, $\\, $\\>>);
|
||||
%% backspace
|
||||
json_escape(<<$\b, Rest/binary>>, Opts, Acc) ->
|
||||
json_escape(Rest, Opts, <<Acc/binary, $\\, $b>>);
|
||||
%% form feed
|
||||
json_escape(<<$\f, Rest/binary>>, Opts, Acc) ->
|
||||
json_escape(Rest, Opts, <<Acc/binary, $\\, $f>>);
|
||||
%% newline
|
||||
json_escape(<<$\n, Rest/binary>>, Opts, Acc) ->
|
||||
json_escape(Rest, Opts, <<Acc/binary, $\\, $n>>);
|
||||
%% cr
|
||||
json_escape(<<$\r, Rest/binary>>, Opts, Acc) ->
|
||||
json_escape(Rest, Opts, <<Acc/binary, $\\, $r>>);
|
||||
%% tab
|
||||
json_escape(<<$\t, Rest/binary>>, Opts, Acc) ->
|
||||
json_escape(Rest, Opts, <<Acc/binary, $\\, $t>>);
|
||||
%% other control characters
|
||||
json_escape(<<C/utf8, Rest/binary>>, Opts, Acc) when C >= 0, C < $\s ->
|
||||
json_escape(Rest,
|
||||
Opts,
|
||||
<<Acc/binary, (unicode:characters_to_binary(json_escape_sequence(C)))/binary>>
|
||||
);
|
||||
%% escape forward slashes -- optionally -- to faciliate microsoft's retarded
|
||||
%% date format
|
||||
json_escape(<<$/, Rest/binary>>, Opts=#opts{escape_forward_slash=true}, Acc) ->
|
||||
json_escape(Rest, Opts, <<Acc/binary, $\\, $/>>);
|
||||
%% escape u+2028 and u+2029 to avoid problems with jsonp
|
||||
json_escape(<<C/utf8, Rest/binary>>, Opts, Acc)
|
||||
when C == 16#2028; C == 16#2029 ->
|
||||
json_escape(Rest,
|
||||
Opts,
|
||||
<<Acc/binary, (unicode:characters_to_binary(json_escape_sequence(C)))/binary>>
|
||||
);
|
||||
%% any other legal codepoint
|
||||
json_escape(<<C/utf8, Rest/binary>>, Opts, Acc) ->
|
||||
json_escape(Rest, Opts, <<Acc/binary, C/utf8>>);
|
||||
json_escape(<<>>, _Opts, Acc) ->
|
||||
Acc;
|
||||
json_escape(Rest, Opts, Acc) ->
|
||||
erlang:error(badarg, [Rest, Opts, Acc]).
|
||||
|
||||
-define(control_character(X),
|
||||
<<H:L/binary, X, T/binary>> ->
|
||||
json_escape(
|
||||
<<H/binary, (unicode:characters_to_binary(json_escape_sequence(X)))/binary, T/binary>>,
|
||||
Opts,
|
||||
L + 6,
|
||||
Len + 5
|
||||
)
|
||||
).
|
||||
|
||||
json_escape(Str, Opts, L, Len) when L < Len ->
|
||||
case Str of
|
||||
?control_character(0);
|
||||
?control_character(1);
|
||||
?control_character(2);
|
||||
?control_character(3);
|
||||
?control_character(4);
|
||||
?control_character(5);
|
||||
?control_character(6);
|
||||
?control_character(7);
|
||||
<<H:L/binary, $\b, T/binary>> -> json_escape(<<H/binary, $\\, $b, T/binary>>, Opts, L + 2, Len + 1);
|
||||
<<H:L/binary, $\t, T/binary>> -> json_escape(<<H/binary, $\\, $t, T/binary>>, Opts, L + 2, Len + 1);
|
||||
<<H:L/binary, $\n, T/binary>> -> json_escape(<<H/binary, $\\, $n, T/binary>>, Opts, L + 2, Len + 1);
|
||||
?control_character(11);
|
||||
<<H:L/binary, $\f, T/binary>> -> json_escape(<<H/binary, $\\, $f, T/binary>>, Opts, L + 2, Len + 1);
|
||||
<<H:L/binary, $\r, T/binary>> -> json_escape(<<H/binary, $\\, $r, T/binary>>, Opts, L + 2, Len + 1);
|
||||
?control_character(14);
|
||||
?control_character(15);
|
||||
?control_character(16);
|
||||
?control_character(17);
|
||||
?control_character(18);
|
||||
?control_character(19);
|
||||
?control_character(20);
|
||||
?control_character(21);
|
||||
?control_character(22);
|
||||
?control_character(23);
|
||||
?control_character(24);
|
||||
?control_character(25);
|
||||
?control_character(26);
|
||||
?control_character(27);
|
||||
?control_character(28);
|
||||
?control_character(29);
|
||||
?control_character(30);
|
||||
?control_character(31);
|
||||
<<_:L/binary, 32, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 33, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<H:L/binary, $\", T/binary>> -> json_escape(<<H/binary, $\\, $", T/binary>>, Opts, L + 2, Len + 1);
|
||||
<<_:L/binary, 35, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 36, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 37, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 38, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 39, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 40, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 41, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 42, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 43, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 44, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 45, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 46, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<H:L/binary, $/, T/binary>> ->
|
||||
case Opts#opts.escape_forward_slash of
|
||||
true ->
|
||||
json_escape(<<H/binary, $\\, $/, T/binary>>, Opts, L + 2, Len + 1);
|
||||
false ->
|
||||
json_escape(<<H/binary, $/, T/binary>>, Opts, L + 1, Len)
|
||||
end;
|
||||
<<_:L/binary, 48, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 49, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 50, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 51, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 52, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 53, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 54, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 55, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 56, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 57, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 58, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 59, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 60, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 61, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 62, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 63, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 64, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 65, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 66, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 67, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 68, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 69, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 70, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 71, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 72, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 73, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 74, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 75, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 76, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 77, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 78, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 79, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 80, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 81, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 82, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 83, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 84, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 85, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 86, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 87, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 88, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 89, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 90, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 91, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<H:L/binary, $\\, T/binary>> -> json_escape(<<H/binary, $\\, $\\, T/binary>>, Opts, L + 2, Len + 1);
|
||||
<<_:L/binary, 93, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 94, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 95, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 96, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 97, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 98, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 99, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 100, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 101, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 102, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 103, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 104, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 105, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 106, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 107, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 108, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 109, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 110, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 111, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 112, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 113, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 114, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 115, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 116, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 117, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 118, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 119, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 120, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 121, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 122, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 123, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 124, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 125, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 126, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, 127, _/binary>> -> json_escape(Str, Opts, L + 1, Len);
|
||||
<<H:L/binary, 16#2028/utf8, T/binary>> ->
|
||||
case Opts#opts.no_jsonp_escapes of
|
||||
true ->
|
||||
json_escape(<<H/binary, 16#2028/utf8, T/binary>>, Opts, L + 3, Len);
|
||||
false ->
|
||||
B = unicode:characters_to_binary(json_escape_sequence(16#2028)),
|
||||
json_escape(<<H/binary, B/binary, T/binary>>, Opts, L + size(B), Len + size(B) - size(<<16#2028/utf8>>))
|
||||
end;
|
||||
<<H:L/binary, 16#2029/utf8, T/binary>> ->
|
||||
case Opts#opts.no_jsonp_escapes of
|
||||
true ->
|
||||
json_escape(<<H/binary, 16#2029/utf8, T/binary>>, Opts, L + 3, Len);
|
||||
false ->
|
||||
B = unicode:characters_to_binary(json_escape_sequence(16#2029)),
|
||||
json_escape(<<H/binary, B/binary, T/binary>>, Opts, L + size(B), Len + size(B) - size(<<16#2029/utf8>>))
|
||||
end;
|
||||
<<_:L/binary, X/utf8, _/binary>> when X < 16#0080 ->
|
||||
json_escape(Str, Opts, L + 1, Len);
|
||||
<<_:L/binary, X/utf8, _/binary>> when X < 16#0800 ->
|
||||
json_escape(Str, Opts, L + 2, Len);
|
||||
<<_:L/binary, X/utf8, _/binary>> when X < 16#10000 ->
|
||||
json_escape(Str, Opts, L + 3, Len);
|
||||
<<_:L/binary, _/utf8, _/binary>> ->
|
||||
json_escape(Str, Opts, L + 4, Len);
|
||||
<<H:L/binary, 237, X, _, T/binary>> when X >= 160 ->
|
||||
case Opts#opts.loose_unicode of
|
||||
true -> json_escape(<<H/binary, 16#fffd/utf8, T/binary>>, Opts, L + 3, Len);
|
||||
false -> erlang:error(badarg, [Str, Opts])
|
||||
end;
|
||||
<<H:L/binary, _, T/binary>> ->
|
||||
case Opts#opts.loose_unicode of
|
||||
true -> json_escape(<<H/binary, 16#fffd/utf8, T/binary>>, Opts, L + 3, Len + 2);
|
||||
false -> erlang:error(badarg, [Str, Opts])
|
||||
end
|
||||
end;
|
||||
json_escape(Str, _, L, Len) when L =:= Len ->
|
||||
Str.
|
||||
|
||||
|
||||
%% convert a codepoint to it's \uXXXX equiv.
|
||||
json_escape_sequence(X) ->
|
||||
<<A:4, B:4, C:4, D:4>> = <<X:16>>,
|
||||
[$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))].
|
||||
unicode:characters_to_binary([$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))]).
|
||||
|
||||
|
||||
to_hex(10) -> $a;
|
||||
|
@ -141,27 +300,55 @@ to_hex(X) -> X + 48. %% ascii "1" is [49], "2" is [50], etc...
|
|||
binary_escape_test_() ->
|
||||
[
|
||||
{"json string escaping",
|
||||
?_assert(json_escape(
|
||||
<<"\"\\\b\f\n\r\t">>, #opts{}
|
||||
) =:= <<"\\\"\\\\\\b\\f\\n\\r\\t">>
|
||||
?_assertEqual(
|
||||
json_escape(<<"\"\\\b\f\n\r\t">>, #opts{}),
|
||||
<<"\\\"\\\\\\b\\f\\n\\r\\t">>
|
||||
)
|
||||
},
|
||||
{"json string hex escape",
|
||||
?_assert(json_escape(
|
||||
<<1, 2, 3, 11, 26, 30, 31>>, #opts{}
|
||||
) =:= <<"\\u0001\\u0002\\u0003\\u000b\\u001a\\u001e\\u001f">>
|
||||
?_assertEqual(
|
||||
json_escape(<<0, 1, 2, 3, 11, 26, 30, 31>>, #opts{}),
|
||||
<<"\\u0000\\u0001\\u0002\\u0003\\u000b\\u001a\\u001e\\u001f">>
|
||||
)
|
||||
},
|
||||
{"jsonp protection",
|
||||
?_assert(json_escape(
|
||||
<<226, 128, 168, 226, 128, 169>>, #opts{}
|
||||
) =:= <<"\\u2028\\u2029">>
|
||||
?_assertEqual(
|
||||
json_escape(<<226, 128, 168, 226, 128, 169>>, #opts{}),
|
||||
<<"\\u2028\\u2029">>
|
||||
)
|
||||
},
|
||||
{"no jsonp escapes",
|
||||
?_assertEqual(
|
||||
json_escape(<<226, 128, 168, 226, 128, 169>>, #opts{no_jsonp_escapes=true}),
|
||||
<<226, 128, 168, 226, 128, 169>>
|
||||
)
|
||||
},
|
||||
{"microsoft i hate your date format",
|
||||
?_assert(json_escape(<<"/Date(1303502009425)/">>,
|
||||
#opts{escape_forward_slash=true}
|
||||
) =:= <<"\\/Date(1303502009425)\\/">>
|
||||
?_assertEqual(
|
||||
json_escape(<<"/Date(1303502009425)/">>, #opts{escape_forward_slash=true}),
|
||||
<<"\\/Date(1303502009425)\\/">>
|
||||
)
|
||||
},
|
||||
{"bad utf8",
|
||||
?_assertError(badarg, json_escape(<<32, 64, 128, 255>>, #opts{}))
|
||||
},
|
||||
{"bad utf8 ok",
|
||||
?_assertEqual(
|
||||
json_escape(<<32, 64, 128, 255>>, #opts{loose_unicode=true}),
|
||||
<<32, 64, 16#fffd/utf8, 16#fffd/utf8>>
|
||||
)
|
||||
},
|
||||
{"bad surrogate", ?_assertError(badarg, json_escape(<<237, 160, 127>>, #opts{}))},
|
||||
{"bad surrogate ok",
|
||||
?_assertEqual(
|
||||
json_escape(<<237, 160, 127>>, #opts{loose_unicode=true}),
|
||||
<<16#fffd/utf8>>
|
||||
)
|
||||
},
|
||||
{"all sizes of codepoints",
|
||||
?_assertEqual(
|
||||
json_escape(unicode:characters_to_binary([0, 32, 16#80, 16#800, 16#10000]), #opts{}),
|
||||
<<"\\u0000", 32/utf8, 16#80/utf8, 16#800/utf8, 16#10000/utf8>>
|
||||
)
|
||||
}
|
||||
].
|
||||
|
|
|
@ -169,11 +169,7 @@ term_true_test_() ->
|
|||
{"empty array", ?_assert(is_term([], []))},
|
||||
{"whitespace", ?_assert(is_term([ true ], []))},
|
||||
{"nested terms",
|
||||
?_assert(is_term(
|
||||
[[{x, [[{}], [{}], [{}]]}, {y, [{}]}], [{}], [[[]]]],
|
||||
[]
|
||||
)
|
||||
)
|
||||
?_assert(is_term([[{x, [[{}], [{}], [{}]]}, {y, [{}]}], [{}], [[[]]]], []))
|
||||
},
|
||||
{"numbers",
|
||||
?_assert(is_term([-1.0, -1, -0, 0, 1.0e-1, 1, 1.0, 1.0e1], []))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue