This commit is contained in:
alisdair sullivan 2014-12-09 16:27:15 -08:00
commit e751e3324f
14 changed files with 547 additions and 428 deletions

View file

@ -1,3 +1,8 @@
v2.4.0
* enough performance improvements to justify a new version. 2-3x
speedup depending on mode of operation
v2.3.1
* fixes an issue where astral plane json escape sequences were

View file

@ -1,4 +1,4 @@
# jsx (v2.3.1) #
# jsx (v2.4) #
an erlang application for consuming, producing and manipulating [json][json].
inspired by [yajl][yajl]
@ -387,10 +387,6 @@ additional options beyond these. see
control codes and problematic codepoints and replacing them with the
appropriate escapes
- `repeat_keys`
this flag circumvents checking for repeated keys in generated json
- `stream`
see [incomplete input](#incomplete-input)

View file

@ -1 +0,0 @@
maps:keys(#{0 => false, 1 => true}) == [0,1].

View file

@ -1,30 +0,0 @@
defmodule JSX.Mixfile do
use Mix.Project
def project do
[
app: :jsx,
version: "2.3.0",
description: "an erlang application for consuming, producing and manipulating json. inspired by yajl",
package: package
]
end
defp package do
[
files: [
"CHANGES.md",
"LICENSE",
"package.exs",
"README.md",
"rebar.config",
"rebar.config.script",
"config",
"src"
],
contributors: ["alisdair sullivan"],
links: %{"github" => "https://github.com/talentdeficit/jsx"},
licenses: ["MIT"]
]
end
end

View file

@ -1,2 +1,4 @@
% uncomment to disable encoding support for erlang maps
% {jsx_nomaps, true}.
{erl_opts, [
{platform_define, "R14|R15", 'no_binary_to_whatever'},
{platform_define, "^((?!R1[456]).)*$", 'maps_support'}
]}.

View file

@ -1,9 +0,0 @@
case os:getenv("JSX_NOMAPS") or proplists:get_value(jsx_nomaps, CONFIG, false) of
false ->
try file:script("config/maps") of
{ok, true} -> [{erl_opts, [{d, maps_support}]}] ++ CONFIG;
_ -> CONFIG
catch _:_ -> CONFIG
end;
_ -> CONFIG
end.

View file

@ -1,7 +1,7 @@
{application, jsx,
[
{description, "a streaming, evented json parsing toolkit"},
{vsn, "2.3.1"},
{vsn, "2.4.0"},
{modules, [
jsx,
jsx_encoder,

View file

@ -63,12 +63,14 @@ parse_config([unescaped_jsonp|Rest], Config) ->
parse_config(Rest, Config#config{unescaped_jsonp=true});
parse_config([dirty_strings|Rest], Config) ->
parse_config(Rest, Config#config{dirty_strings=true});
%% retained for backwards compat, now does nothing however
parse_config([repeat_keys|Rest], Config) ->
parse_config(Rest, Config#config{repeat_keys=true});
parse_config(Rest, Config);
parse_config([uescape|Rest], Config) ->
parse_config(Rest, Config#config{uescape=true});
parse_config([strict|Rest], Config) ->
parse_config(Rest, Config#config{strict_comments=true,
parse_config(Rest, Config#config{
strict_comments=true,
strict_commas=true,
strict_utf8=true,
strict_single_quotes=true,
@ -190,7 +192,6 @@ config_test_() ->
escaped_strings = true,
unescaped_jsonp = true,
dirty_strings = true,
repeat_keys = true,
strict_comments = true,
strict_commas = true,
strict_utf8 = true,
@ -274,7 +275,6 @@ config_to_list_test_() ->
escaped_strings,
unescaped_jsonp,
dirty_strings,
repeat_keys,
stream,
uescape,
strict
@ -284,7 +284,6 @@ config_to_list_test_() ->
escaped_strings = true,
unescaped_jsonp = true,
dirty_strings = true,
repeat_keys = true,
strict_comments = true,
strict_utf8 = true,
strict_single_quotes = true,

View file

@ -3,7 +3,6 @@
escaped_strings = false :: boolean(),
unescaped_jsonp = false :: boolean(),
dirty_strings = false :: boolean(),
repeat_keys = false :: boolean(),
strict_comments = false :: boolean(),
strict_commas = false :: boolean(),
strict_utf8 = false :: boolean(),

View file

@ -58,10 +58,7 @@ resume(Rest, State, Handler, Acc, Stack, Config) ->
colon -> colon(Rest, Handler, Stack, Config);
key -> key(Rest, Handler, Stack, Config);
string -> string(Rest, Handler, Acc, Stack, Config);
integer -> integer(Rest, Handler, Acc, Stack, Config);
decimal -> decimal(Rest, Handler, Acc, Stack, Config);
exp -> exp(Rest, Handler, Acc, Stack, Config);
zero -> zero(Rest, Handler, Acc, Stack, Config);
number -> number(Rest, Handler, Acc, Stack, Config);
true -> true(Rest, Handler, Stack, Config);
false -> false(Rest, Handler, Stack, Config);
null -> null(Rest, Handler, Stack, Config);
@ -119,10 +116,6 @@ resume(Rest, State, Handler, Acc, Stack, Config) ->
Symbol >= $1 andalso Symbol =< $9
).
-define(is_whitespace(Symbol),
Symbol =:= ?space; Symbol =:= ?tab; Symbol =:= ?cr; Symbol =:= ?newline
).
%% error is a macro so the stack trace shows the error site when possible
-ifndef(error).
@ -177,26 +170,54 @@ start(Bin, Handler, Stack, Config) ->
value(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
string(Rest, Handler, Stack, Config);
value(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
string(Rest, Handler, [singlequote|Stack], Config);
value(<<?space, Rest/binary>>, Handler, Stack, Config) ->
value(Rest, Handler, Stack, Config);
value(<<?start_object, Rest/binary>>, Handler, Stack, Config) ->
object(Rest, handle_event(start_object, Handler, Config), [key|Stack], Config);
value(<<?start_array, Rest/binary>>, Handler, Stack, Config) ->
array(Rest, handle_event(start_array, Handler, Config), [array|Stack], Config);
value(<<$t, $r, $u, $e, Rest/binary>>, Handler, Stack, Config) ->
maybe_done(Rest, handle_event({literal, true}, Handler, Config), Stack, Config);
value(<<$f, $a, $l, $s, $e, Rest/binary>>, Handler, Stack, Config) ->
maybe_done(Rest, handle_event({literal, false}, Handler, Config), Stack, Config);
value(<<$n, $u, $l, $l, Rest/binary>>, Handler, Stack, Config) ->
maybe_done(Rest, handle_event({literal, null}, Handler, Config), Stack, Config);
value(<<?zero, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [?zero], [zero|Stack], Config);
value(<<$1, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [$1], [integer|Stack], Config);
value(<<$2, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [$2], [integer|Stack], Config);
value(<<$3, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [$3], [integer|Stack], Config);
value(<<$4, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [$4], [integer|Stack], Config);
value(<<$5, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [$5], [integer|Stack], Config);
value(<<$6, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [$6], [integer|Stack], Config);
value(<<$7, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [$7], [integer|Stack], Config);
value(<<$8, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [$8], [integer|Stack], Config);
value(<<$9, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [$9], [integer|Stack], Config);
value(<<?negative, Rest/binary>>, Handler, Stack, Config) ->
number(Rest, Handler, [$-], [negative|Stack], Config);
value(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
value(Rest, Handler, Stack, Config);
value(<<$t, Rest/binary>>, Handler, Stack, Config) ->
true(Rest, Handler, Stack, Config);
value(<<$f, Rest/binary>>, Handler, Stack, Config) ->
false(Rest, Handler, Stack, Config);
value(<<$n, Rest/binary>>, Handler, Stack, Config) ->
null(Rest, Handler, Stack, Config);
value(<<?negative, Rest/binary>>, Handler, Stack, Config) ->
negative(Rest, Handler, [$-], Stack, Config);
value(<<?zero, Rest/binary>>, Handler, Stack, Config) ->
zero(Rest, Handler, [$0], Stack, Config);
value(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_nonzero(S) ->
integer(Rest, Handler, [S], Stack, Config);
value(<<?start_object, Rest/binary>>, Handler, Stack, Config) ->
object(Rest, handle_event(start_object, Handler, Config), [key|Stack], Config);
value(<<?start_array, Rest/binary>>, Handler, Stack, Config) ->
array(Rest, handle_event(start_array, Handler, Config), [array|Stack], Config);
value(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
value(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
value(Rest, Handler, Stack, Config);
value(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
value(Rest, Handler, Stack, Config);
value(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
string(Rest, Handler, [singlequote|Stack], Config);
value(<<?end_array, _/binary>> = Rest, Handler, Stack, Config=#config{strict_commas=false}) ->
maybe_done(Rest, Handler, Stack, Config);
value(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
@ -215,12 +236,18 @@ value(Bin, Handler, Stack, Config) ->
object(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
string(Rest, Handler, Stack, Config);
object(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
string(Rest, Handler, [singlequote|Stack], Config);
object(<<?space, Rest/binary>>, Handler, Stack, Config) ->
object(Rest, Handler, Stack, Config);
object(<<?end_object, Rest/binary>>, Handler, [key|Stack], Config) ->
maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config);
object(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
object(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
object(Rest, Handler, Stack, Config);
object(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
object(Rest, Handler, Stack, Config);
object(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
object(Rest, Handler, Stack, Config);
object(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
string(Rest, Handler, [singlequote|Stack], Config);
object(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
?error(object, <<?solidus, Rest/binary>>, Handler, Stack, Config);
object(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
@ -237,7 +264,13 @@ object(Bin, Handler, Stack, Config) ->
array(<<?end_array, Rest/binary>>, Handler, [array|Stack], Config) ->
maybe_done(Rest, handle_event(end_array, Handler, Config), Stack, Config);
array(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
array(<<?space, Rest/binary>>, Handler, Stack, Config) ->
array(Rest, Handler, Stack, Config);
array(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
array(Rest, Handler, Stack, Config);
array(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
array(Rest, Handler, Stack, Config);
array(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
array(Rest, Handler, Stack, Config);
array(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
value(<<?solidus, Rest/binary>>, Handler, Stack, Config);
@ -255,7 +288,13 @@ array(Bin, Handler, Stack, Config) ->
colon(<<?colon, Rest/binary>>, Handler, [key|Stack], Config) ->
value(Rest, Handler, [object|Stack], Config);
colon(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
colon(<<?space, Rest/binary>>, Handler, Stack, Config) ->
colon(Rest, Handler, Stack, Config);
colon(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
colon(Rest, Handler, Stack, Config);
colon(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
colon(Rest, Handler, Stack, Config);
colon(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
colon(Rest, Handler, Stack, Config);
colon(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
?error(colon, <<?solidus, Rest/binary>>, Handler, Stack, Config);
@ -273,12 +312,18 @@ colon(Bin, Handler, Stack, Config) ->
key(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
string(Rest, Handler, Stack, Config);
key(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
string(Rest, Handler, [singlequote|Stack], Config);
key(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
key(<<?space, Rest/binary>>, Handler, Stack, Config) ->
key(Rest, Handler, Stack, Config);
key(<<?end_object, Rest/binary>>, Handler, [key|Stack], Config=#config{strict_commas=false}) ->
maybe_done(<<?end_object, Rest/binary>>, Handler, [object|Stack], Config);
key(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
key(Rest, Handler, Stack, Config);
key(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
key(Rest, Handler, Stack, Config);
key(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
key(Rest, Handler, Stack, Config);
key(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
string(Rest, Handler, [singlequote|Stack], Config);
key(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
?error(key, <<?solidus, Rest/binary>>, Handler, Stack, Config);
key(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
@ -310,10 +355,17 @@ string(<<?solidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
string(Rest, Handler, [Acc, maybe_replace(?solidus, Config)], Stack, Config);
string(<<?rsolidus/utf8, Rest/binary>>, Handler, Acc, Stack, Config) ->
unescape(Rest, Handler, Acc, Stack, Config);
string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config=#config{uescape=true}) when X >= 16#80 ->
string(Rest, Handler, [Acc, maybe_replace(X, Config)], Stack, Config);
string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X == 16#2028; X == 16#2029 ->
string(Rest, Handler, [Acc, maybe_replace(X, Config)], Stack, Config);
string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config=#config{uescape=true}) ->
case X of
X when X < 16#80 -> string(Rest, Handler, [Acc, X], Stack, Config);
X -> string(Rest, Handler, [Acc, json_escape_sequence(X)], Stack, Config)
end;
%% u+2028
string(<<226, 128, 168, Rest/binary>>, Handler, Acc, Stack, Config) ->
string(Rest, Handler, [Acc, maybe_replace(16#2028, Config)], Stack, Config);
%% u+2029
string(<<226, 128, 169, Rest/binary>>, Handler, Acc, Stack, Config) ->
string(Rest, Handler, [Acc, maybe_replace(16#2029, Config)], Stack, Config);
string(<<_/utf8, _/binary>> = Bin, Handler, Acc, Stack, Config) ->
Size = count(Bin, 0, Config),
<<Clean:Size/binary, Rest/binary>> = Bin,
@ -327,9 +379,9 @@ string(<<239, 191, 190, Rest/binary>>, Handler, Acc, Stack, Config) ->
string(Rest, Handler, [Acc, <<16#fffe/utf8>>], Stack, Config);
string(<<239, 191, 191, Rest/binary>>, Handler, Acc, Stack, Config) ->
string(Rest, Handler, [Acc, <<16#ffff/utf8>>], Stack, Config);
%% partial utf8 codepoints
string(<<>>, Handler, Acc, Stack, Config) ->
incomplete(string, <<>>, Handler, Acc, Stack, Config);
%% partial utf8 codepoints
string(<<X>>, Handler, Acc, Stack, Config) when X >= 2#11000000 ->
incomplete(string, <<X>>, Handler, Acc, Stack, Config);
string(<<X, Y>>, Handler, Acc, Stack, Config) when X >= 2#11100000, Y >= 2#10000000 ->
@ -614,13 +666,16 @@ count(<<127, Rest/binary>>, N, Config) ->
count(<<_, Rest/binary>>, N, Config=#config{dirty_strings=true}) ->
count(Rest, N + 1, Config);
count(<<_/utf8, _/binary>>, N, #config{uescape=true}) -> N;
count(<<X/utf8, Rest/binary>>, N, Config) when X < 16#800 ->
count(Rest, N + 2, Config);
count(<<X/utf8, _/binary>>, N, _) when X == 16#2028; X == 16#2029 -> N;
count(<<X/utf8, Rest/binary>>, N, Config) when X < 16#10000 ->
count(Rest, N + 3, Config);
count(<<_/utf8, Rest/binary>>, N, Config) ->
count(Rest, N + 4, Config);
%% u+2028
count(<<226, 128, 168, _/binary>>, N, _) -> N;
%% u+2029
count(<<226, 128, 169, _/binary>>, N, _) -> N;
count(<<X/utf8, Rest/binary>>, N, Config) ->
case X of
X when X < 16#800 -> count(Rest, N + 2, Config);
X when X < 16#10000 -> count(Rest, N + 3, Config);
_ -> count(Rest, N + 4, Config)
end;
count(_, N, _) -> N.
@ -666,9 +721,9 @@ strip_continuations(<<Rest/binary>>, Handler, Acc, Stack, Config, _) ->
%% this all gets really gross and should probably eventually be folded into
%% but for now it fakes being part of string on incompletes and errors
unescape(<<?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config=#config{dirty_strings=true}) ->
string(<<?rsolidus, Rest/binary>>, Handler, [Acc, ?rsolidus], Stack, Config);
string(<<?rsolidus, Rest/binary>>, Handler, [Acc, <<?rsolidus>>], Stack, Config);
unescape(<<C, Rest/binary>>, Handler, Acc, Stack, Config=#config{dirty_strings=true}) ->
string(Rest, Handler, [Acc, ?rsolidus, C], Stack, Config);
string(Rest, Handler, [Acc, <<?rsolidus, C>>], Stack, Config);
unescape(<<$b, Rest/binary>>, Handler, Acc, Stack, Config) ->
string(Rest, Handler, [Acc, maybe_replace($\b, Config)], Stack, Config);
unescape(<<$f, Rest/binary>>, Handler, Acc, Stack, Config) ->
@ -682,7 +737,7 @@ unescape(<<$t, Rest/binary>>, Handler, Acc, Stack, Config) ->
unescape(<<?doublequote, Rest/binary>>, Handler, Acc, Stack, Config) ->
string(Rest, Handler, [Acc, maybe_replace($\", Config)], Stack, Config);
unescape(<<?singlequote, Rest/binary>>, Handler, Acc, Stack, Config=#config{strict_single_quotes=false}) ->
string(Rest, Handler, [Acc, ?singlequote], Stack, Config);
string(Rest, Handler, [Acc, <<?singlequote>>], Stack, Config);
unescape(<<?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
string(Rest, Handler, [Acc, maybe_replace($\\, Config)], Stack, Config);
unescape(<<?solidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
@ -733,7 +788,7 @@ unescape(Bin, Handler, Acc, Stack, Config) ->
true -> incomplete(string, <<?rsolidus/utf8, Bin/binary>>, Handler, Acc, Stack, Config);
false -> case Config#config.strict_escapes of
true -> ?error(string, <<?rsolidus, Bin/binary>>, Handler, Acc, Stack, Config);
false -> string(Bin, Handler, [Acc, ?rsolidus], Stack, Config)
false -> string(Bin, Handler, [Acc, <<?rsolidus>>], Stack, Config)
end
end.
@ -746,19 +801,19 @@ is_partial_escape(<<>>) -> true;
is_partial_escape(_) -> false.
maybe_replace(C, #config{dirty_strings=true}) -> C;
maybe_replace($\b, #config{escaped_strings=true}) -> [$\\, $b];
maybe_replace($\t, #config{escaped_strings=true}) -> [$\\, $t];
maybe_replace($\n, #config{escaped_strings=true}) -> [$\\, $n];
maybe_replace($\f, #config{escaped_strings=true}) -> [$\\, $f];
maybe_replace($\r, #config{escaped_strings=true}) -> [$\\, $r];
maybe_replace($\", #config{escaped_strings=true}) -> [$\\, $\"];
maybe_replace(C, #config{dirty_strings=true}) -> <<C>>;
maybe_replace($\b, #config{escaped_strings=true}) -> <<$\\, $b>>;
maybe_replace($\t, #config{escaped_strings=true}) -> <<$\\, $t>>;
maybe_replace($\n, #config{escaped_strings=true}) -> <<$\\, $n>>;
maybe_replace($\f, #config{escaped_strings=true}) -> <<$\\, $f>>;
maybe_replace($\r, #config{escaped_strings=true}) -> <<$\\, $r>>;
maybe_replace($\", #config{escaped_strings=true}) -> <<$\\, $\">>;
maybe_replace($/, Config=#config{escaped_strings=true}) ->
case Config#config.escaped_forward_slashes of
true -> [$\\, $/]
; false -> $/
true -> <<$\\, $/>>
; false -> <<$/>>
end;
maybe_replace($\\, #config{escaped_strings=true}) -> [$\\, $\\];
maybe_replace($\\, #config{escaped_strings=true}) -> <<$\\, $\\>>;
maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 ->
case Config#config.unescaped_jsonp of
true -> <<X/utf8>>
@ -766,20 +821,17 @@ maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X ==
end;
maybe_replace(X, #config{escaped_strings=true}) when X < 32 ->
json_escape_sequence(X);
%% escaped even if no other escaping requested!
maybe_replace(X, #config{uescape=true}) when X >= 16#80 ->
json_escape_sequence(X);
maybe_replace(X, _Config) -> <<X/utf8>>.
%% convert a codepoint to it's \uXXXX equiv.
json_escape_sequence(X) when X < 65536 ->
<<A:4, B:4, C:4, D:4>> = <<X:16>>,
[$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))];
<<$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))>>;
json_escape_sequence(X) ->
Adjusted = X - 16#10000,
<<A:10, B:10>> = <<Adjusted:20>>,
json_escape_sequence(A + 16#d800) ++ json_escape_sequence(B + 16#dc00).
[json_escape_sequence(A + 16#d800), json_escape_sequence(B + 16#dc00)].
%% ascii "1" is [49], "2" is [50], etc...
@ -792,93 +844,174 @@ to_hex(15) -> $f;
to_hex(X) -> X + 48.
%% like in strings, there's some pseudo states in here that will never
%% show up in errors or incompletes. some show up in value, some show
%% up in integer, decimal or exp
negative(<<$0, Rest/binary>>, Handler, Acc, Stack, Config) ->
zero(Rest, Handler, acc_seq(Acc, $0), Stack, Config);
negative(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when ?is_nonzero(S) ->
integer(Rest, Handler, acc_seq(Acc, S), Stack, Config);
negative(<<>>, Handler, [?negative], Stack, Config) ->
incomplete(value, <<?negative>>, Handler, Stack, Config);
negative(Bin, Handler, Acc, Stack, Config) ->
?error(value, <<?negative, Bin/binary>>, Handler, Acc, Stack, Config).
number(<<$e, Rest/binary>>, Handler, Acc, [integer|Stack], Config) ->
number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
number(<<$E, Rest/binary>>, Handler, Acc, [integer|Stack], Config) ->
number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
number(<<$e, Rest/binary>>, Handler, Acc, [zero|Stack], Config) ->
number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
number(<<$E, Rest/binary>>, Handler, Acc, [zero|Stack], Config) ->
number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
number(<<>>, Handler, Acc, [State|Stack], Config=#config{stream=false}) ->
NumType = case State of
zero -> integer;
integer -> integer;
decimal -> float;
exp -> float
end,
finish_number(<<>>, Handler, {NumType, iolist_to_binary(Acc)}, Stack, Config);
number(<<>>, Handler, Acc, Stack, Config) ->
incomplete(number, <<>>, Handler, Acc, Stack, Config);
number(Bin, Handler, Acc, [State|Stack], Config) ->
Counted = case State of
zero -> zero(Bin, 0);
integer -> integer(Bin, 0);
negative -> negative(Bin, 0);
initialdecimal -> initialdecimal(Bin, 0);
decimal -> decimal(Bin, 0);
e -> e(Bin, 0);
ex -> ex(Bin, 0);
exp -> exp(Bin, 0)
end,
case Counted of
{finish_integer, Size} ->
<<Clean:Size/binary, Rest/binary>> = Bin,
finish_number(Rest, Handler, {integer, iolist_to_binary([Acc, Clean])}, Stack, Config);
{finish_float, Size} ->
<<Clean:Size/binary, Rest/binary>> = Bin,
finish_number(Rest, Handler, {float, iolist_to_binary([Acc, Clean])}, Stack, Config);
{error, Size} ->
<<Clean:Size/binary, Rest/binary>> = Bin,
?error(number, Rest, Handler, [Acc, Clean], Stack, Config);
{NewState, Size} ->
<<Clean:Size/binary, Rest/binary>> = Bin,
number(Rest, Handler, [Acc, Clean], [NewState|Stack], Config)
end.
zero(<<?decimalpoint, Rest/binary>>, Handler, Acc, Stack, Config) ->
decimal(Rest, Handler, acc_seq(Acc, ?decimalpoint), Stack, Config);
zero(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= $e; S =:= $E ->
e(Rest, Handler, acc_seq(Acc, ".0e"), Stack, Config);
zero(Bin, Handler, Acc, Stack, Config) ->
finish_number(Bin, Handler, {zero, Acc}, Stack, Config).
zero(<<?decimalpoint, Rest/binary>>, N) -> initialdecimal(Rest, N + 1);
zero(<<>>, N) -> {zero, N};
zero(_, N) -> {finish_integer, N}.
integer(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) ->
integer(Rest, Handler, acc_seq(Acc, S), Stack, Config);
integer(<<?decimalpoint, Rest/binary>>, Handler, Acc, Stack, Config) ->
initialdecimal(Rest, Handler, acc_seq(Acc, ?decimalpoint), Stack, Config);
integer(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= $e; S =:= $E ->
e(Rest, Handler, acc_seq(Acc, ".0e"), Stack, Config);
integer(Bin, Handler, Acc, Stack, Config) ->
finish_number(Bin, Handler, {integer, Acc}, Stack, Config).
integer(<<$0, Rest/binary>>, N) -> integer(Rest, N + 1);
integer(<<$1, Rest/binary>>, N) -> integer(Rest, N + 1);
integer(<<$2, Rest/binary>>, N) -> integer(Rest, N + 1);
integer(<<$3, Rest/binary>>, N) -> integer(Rest, N + 1);
integer(<<$4, Rest/binary>>, N) -> integer(Rest, N + 1);
integer(<<$5, Rest/binary>>, N) -> integer(Rest, N + 1);
integer(<<$6, Rest/binary>>, N) -> integer(Rest, N + 1);
integer(<<$7, Rest/binary>>, N) -> integer(Rest, N + 1);
integer(<<$8, Rest/binary>>, N) -> integer(Rest, N + 1);
integer(<<$9, Rest/binary>>, N) -> integer(Rest, N + 1);
integer(<<?decimalpoint, Rest/binary>>, N) -> initialdecimal(Rest, N + 1);
integer(<<$e, _/binary>>, N) -> {integer, N};
integer(<<$E, _/binary>>, N) -> {integer, N};
integer(<<>>, N) -> {integer, N};
integer(_, N) -> {finish_integer, N}.
initialdecimal(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) ->
decimal(Rest, Handler, acc_seq(Acc, S), Stack, Config);
initialdecimal(<<>>, Handler, [?decimalpoint|Acc], Stack, Config) ->
incomplete(integer, <<?decimalpoint>>, Handler, Acc, Stack, Config);
initialdecimal(Bin, Handler, Acc, Stack, Config) ->
?error(decimal, Bin, Handler, Acc, Stack, Config).
negative(<<$0, Rest/binary>>, N) -> zero(Rest, N + 1);
negative(<<$1, Rest/binary>>, N) -> integer(Rest, N + 1);
negative(<<$2, Rest/binary>>, N) -> integer(Rest, N + 1);
negative(<<$3, Rest/binary>>, N) -> integer(Rest, N + 1);
negative(<<$4, Rest/binary>>, N) -> integer(Rest, N + 1);
negative(<<$5, Rest/binary>>, N) -> integer(Rest, N + 1);
negative(<<$6, Rest/binary>>, N) -> integer(Rest, N + 1);
negative(<<$7, Rest/binary>>, N) -> integer(Rest, N + 1);
negative(<<$8, Rest/binary>>, N) -> integer(Rest, N + 1);
negative(<<$9, Rest/binary>>, N) -> integer(Rest, N + 1);
negative(<<>>, N) -> {negative, N};
negative(_, N) -> {error, N}.
decimal(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) ->
decimal(Rest, Handler, acc_seq(Acc, S), Stack, Config);
decimal(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= $e; S =:= $E ->
e(Rest, Handler, acc_seq(Acc, $e), Stack, Config);
decimal(Bin, Handler, Acc, Stack, Config) ->
finish_number(Bin, Handler, {decimal, Acc}, Stack, Config).
initialdecimal(<<$0, Rest/binary>>, N) -> decimal(Rest, N + 1);
initialdecimal(<<$1, Rest/binary>>, N) -> decimal(Rest, N + 1);
initialdecimal(<<$2, Rest/binary>>, N) -> decimal(Rest, N + 1);
initialdecimal(<<$3, Rest/binary>>, N) -> decimal(Rest, N + 1);
initialdecimal(<<$4, Rest/binary>>, N) -> decimal(Rest, N + 1);
initialdecimal(<<$5, Rest/binary>>, N) -> decimal(Rest, N + 1);
initialdecimal(<<$6, Rest/binary>>, N) -> decimal(Rest, N + 1);
initialdecimal(<<$7, Rest/binary>>, N) -> decimal(Rest, N + 1);
initialdecimal(<<$8, Rest/binary>>, N) -> decimal(Rest, N + 1);
initialdecimal(<<$9, Rest/binary>>, N) -> decimal(Rest, N + 1);
initialdecimal(<<>>, N) -> {initialdecimal, N};
initialdecimal(_, N) -> {error, N}.
e(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) ->
exp(Rest, Handler, acc_seq(Acc, S), Stack, Config);
e(<<Sign, Rest/binary>>, Handler, Acc, Stack, Config) when Sign =:= ?positive; Sign =:= ?negative ->
ex(Rest, Handler, acc_seq(Acc, Sign), Stack, Config);
e(<<>>, Handler, [$e|Acc], Stack, Config) ->
incomplete(decimal, <<$e>>, Handler, Acc, Stack, Config);
e(Bin, Handler, Acc, Stack, Config) ->
?error(decimal, <<$e, Bin/binary>>, Handler, Acc, Stack, Config).
decimal(<<$0, Rest/binary>>, N) -> decimal(Rest, N + 1);
decimal(<<$1, Rest/binary>>, N) -> decimal(Rest, N + 1);
decimal(<<$2, Rest/binary>>, N) -> decimal(Rest, N + 1);
decimal(<<$3, Rest/binary>>, N) -> decimal(Rest, N + 1);
decimal(<<$4, Rest/binary>>, N) -> decimal(Rest, N + 1);
decimal(<<$5, Rest/binary>>, N) -> decimal(Rest, N + 1);
decimal(<<$6, Rest/binary>>, N) -> decimal(Rest, N + 1);
decimal(<<$7, Rest/binary>>, N) -> decimal(Rest, N + 1);
decimal(<<$8, Rest/binary>>, N) -> decimal(Rest, N + 1);
decimal(<<$9, Rest/binary>>, N) -> decimal(Rest, N + 1);
decimal(<<$e, Rest/binary>>, N) -> e(Rest, N + 1);
decimal(<<$E, Rest/binary>>, N) -> e(Rest, N + 1);
decimal(<<>>, N) -> {decimal, N};
decimal(_, N) -> {finish_float, N}.
ex(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) ->
exp(Rest, Handler, acc_seq(Acc, S), Stack, Config);
ex(<<>>, Handler, [S, $e|Acc], Stack, Config) ->
incomplete(decimal, <<$e, S/utf8>>, Handler, Acc, Stack, Config);
ex(Bin, Handler, [S, $e|Acc], Stack, Config) ->
?error(decimal, <<$e, S, Bin/binary>>, Handler, Acc, Stack, Config).
e(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1);
e(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1);
e(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1);
e(<<$3, Rest/binary>>, N) -> exp(Rest, N + 1);
e(<<$4, Rest/binary>>, N) -> exp(Rest, N + 1);
e(<<$5, Rest/binary>>, N) -> exp(Rest, N + 1);
e(<<$6, Rest/binary>>, N) -> exp(Rest, N + 1);
e(<<$7, Rest/binary>>, N) -> exp(Rest, N + 1);
e(<<$8, Rest/binary>>, N) -> exp(Rest, N + 1);
e(<<$9, Rest/binary>>, N) -> exp(Rest, N + 1);
e(<<?positive, Rest/binary>>, N) -> ex(Rest, N + 1);
e(<<?negative, Rest/binary>>, N) -> ex(Rest, N + 1);
e(<<>>, N) -> {e, N};
e(_, N) -> {error, N}.
exp(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) ->
exp(Rest, Handler, acc_seq(Acc, S), Stack, Config);
exp(Bin, Handler, Acc, Stack, Config) ->
finish_number(Bin, Handler, {exp, Acc}, Stack, Config).
ex(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1);
ex(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1);
ex(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1);
ex(<<$3, Rest/binary>>, N) -> exp(Rest, N + 1);
ex(<<$4, Rest/binary>>, N) -> exp(Rest, N + 1);
ex(<<$5, Rest/binary>>, N) -> exp(Rest, N + 1);
ex(<<$6, Rest/binary>>, N) -> exp(Rest, N + 1);
ex(<<$7, Rest/binary>>, N) -> exp(Rest, N + 1);
ex(<<$8, Rest/binary>>, N) -> exp(Rest, N + 1);
ex(<<$9, Rest/binary>>, N) -> exp(Rest, N + 1);
ex(<<>>, N) -> {ex, N};
ex(_, N) -> {error, N}.
acc_seq(Seq, C) when is_list(C) -> lists:reverse(C) ++ Seq;
acc_seq(Seq, C) -> [C] ++ Seq.
exp(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1);
exp(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1);
exp(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1);
exp(<<$3, Rest/binary>>, N) -> exp(Rest, N + 1);
exp(<<$4, Rest/binary>>, N) -> exp(Rest, N + 1);
exp(<<$5, Rest/binary>>, N) -> exp(Rest, N + 1);
exp(<<$6, Rest/binary>>, N) -> exp(Rest, N + 1);
exp(<<$7, Rest/binary>>, N) -> exp(Rest, N + 1);
exp(<<$8, Rest/binary>>, N) -> exp(Rest, N + 1);
exp(<<$9, Rest/binary>>, N) -> exp(Rest, N + 1);
exp(<<>>, N) -> {exp, N};
exp(_, N) -> {finish_float, N}.
finish_number(Rest, Handler, Acc, [], Config=#config{stream=false}) ->
maybe_done(Rest, handle_event(format_number(Acc), Handler, Config), [], Config);
finish_number(<<>>, Handler, {NumType, Acc}, Stack, Config) ->
incomplete(NumType, <<>>, Handler, Acc, Stack, Config);
finish_number(Rest, Handler, Acc, Stack, Config) ->
maybe_done(Rest, handle_event(format_number(Acc), Handler, Config), Stack, Config).
format_number({zero, Acc}) -> {integer, list_to_integer(lists:reverse(Acc))};
format_number({integer, Acc}) -> {integer, list_to_integer(lists:reverse(Acc))};
format_number({decimal, Acc}) -> {float, list_to_float(lists:reverse(Acc))};
format_number({exp, Acc}) -> {float, list_to_float(lists:reverse(Acc))}.
-ifndef(no_binary_to_whatever).
format_number({integer, Acc}) -> {integer, binary_to_integer(Acc)};
format_number({float, Acc}) -> {float, binary_to_float(Acc)}.
-endif.
-ifdef(no_binary_to_whatever).
format_number({integer, Acc}) -> {integer, list_to_integer(unicode:characters_to_list(Acc))};
format_number({float, Acc}) -> {float, list_to_float(unicode:characters_to_list(Acc))}.
-endif.
true(<<$r, $u, $e, Rest/binary>>, Handler, Stack, Config) ->
@ -947,6 +1080,8 @@ comment(Bin, Handler, Resume, Stack, Config) ->
maybe_done(<<Rest/binary>>, Handler, [], Config) ->
done(Rest, handle_event(end_json, Handler, Config), [], Config);
maybe_done(<<?space, Rest/binary>>, Handler, Stack, Config) ->
maybe_done(Rest, Handler, Stack, Config);
maybe_done(<<?end_object, Rest/binary>>, Handler, [object|Stack], Config) ->
maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config);
maybe_done(<<?end_array, Rest/binary>>, Handler, [array|Stack], Config) ->
@ -955,7 +1090,11 @@ maybe_done(<<?comma, Rest/binary>>, Handler, [object|Stack], Config) ->
key(Rest, Handler, [key|Stack], Config);
maybe_done(<<?comma, Rest/binary>>, Handler, [array|_] = Stack, Config) ->
value(Rest, Handler, Stack, Config);
maybe_done(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
maybe_done(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
maybe_done(Rest, Handler, Stack, Config);
maybe_done(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
maybe_done(Rest, Handler, Stack, Config);
maybe_done(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
maybe_done(Rest, Handler, Stack, Config);
maybe_done(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
?error(maybe_done, <<?solidus, Rest/binary>>, Handler, Stack, Config);
@ -971,7 +1110,13 @@ maybe_done(Bin, Handler, Stack, Config) ->
?error(maybe_done, Bin, Handler, Stack, Config).
done(<<S, Rest/binary>>, Handler, [], Config) when ?is_whitespace(S) ->
done(<<?space, Rest/binary>>, Handler, [], Config) ->
done(Rest, Handler, [], Config);
done(<<?newline, Rest/binary>>, Handler, [], Config) ->
done(Rest, Handler, [], Config);
done(<<?tab, Rest/binary>>, Handler, [], Config) ->
done(Rest, Handler, [], Config);
done(<<?cr, Rest/binary>>, Handler, [], Config) ->
done(Rest, Handler, [], Config);
done(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
?error(done, <<?solidus, Rest/binary>>, Handler, Stack, Config);

View file

@ -23,7 +23,7 @@
-module(jsx_encoder).
-export([encoder/3, encode/1, encode/2, unzip/1]).
-export([encoder/3, encode/1, encode/2]).
-spec encoder(Handler::module(), State::any(), Config::list()) -> jsx:encoder().
@ -44,11 +44,10 @@ encode(Term, EntryPoint) -> encode_(Term, EntryPoint).
-endif.
-ifdef(maps_support).
encode(Map, _EntryPoint) when is_map(Map), map_size(Map) < 1 -> [start_object, end_object];
encode(Map, _EntryPoint) when is_map(Map), map_size(Map) < 1 ->
[start_object, end_object];
encode(Term, EntryPoint) when is_map(Term) ->
lists:flatten(
[start_object] ++ [ EntryPoint:encode(T, EntryPoint) || T <- unpack(Term) ] ++ [end_object]
);
[start_object] ++ unpack(Term, EntryPoint);
encode(Term, EntryPoint) -> encode_(Term, EntryPoint).
-endif.
@ -56,28 +55,29 @@ encode_([], _EntryPoint) -> [start_array, end_array];
encode_([{}], _EntryPoint) -> [start_object, end_object];
encode_([{_, _}|_] = Term, EntryPoint) ->
lists:flatten(
[start_object] ++ [ EntryPoint:encode(T, EntryPoint) || T <- unzip(Term) ] ++ [end_object]
);
[start_object] ++ unzip(Term, EntryPoint);
encode_(Term, EntryPoint) when is_list(Term) ->
lists:flatten(
[start_array] ++ [ EntryPoint:encode(T, EntryPoint) || T <- Term ] ++ [end_array]
);
[start_array] ++ unhitch(Term, EntryPoint);
encode_(Else, _EntryPoint) -> [Else].
unzip(List) -> unzip(List, []).
unzip([{K, V}|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) ->
[K] ++ EntryPoint:encode(V, EntryPoint) ++ unzip(Rest, EntryPoint);
unzip([], _) -> [end_object].
unhitch([V|Rest], EntryPoint) ->
EntryPoint:encode(V, EntryPoint) ++ unhitch(Rest, EntryPoint);
unhitch([], _) -> [end_array].
unzip([], Acc) -> lists:reverse(Acc);
unzip([{K, V}|Rest], Acc) when is_binary(K); is_atom(K); is_integer(K) -> unzip(Rest, [V, K] ++ Acc).
-ifdef(maps_support).
unpack(Map) -> unpack(maps:keys(Map), Map, []).
unpack(Map, EntryPoint) -> unpack(Map, maps:keys(Map), EntryPoint).
unpack([], _, Acc) -> lists:reverse(Acc);
unpack([K|Rest], Map, Acc) when is_binary(K); is_atom(K); is_integer(K) ->
unpack(Rest, Map, [maps:get(K, Map), K] ++ Acc).
unpack(Map, [K|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) ->
[K] ++ EntryPoint:encode(maps:get(K, Map), EntryPoint) ++ unpack(Map, Rest, EntryPoint);
unpack(_, [], _) -> [end_object].
-endif.

View file

@ -87,36 +87,26 @@ incomplete(State, Handler, Stack, Config=#config{incomplete_handler=F}) ->
handle_event(Event, {Handler, State}, _Config) -> {Handler, Handler:handle_event(Event, State)}.
value([start_object|Tokens], Handler, Stack, Config) ->
object(Tokens, handle_event(start_object, Handler, Config), [{object, sets:new()}|Stack], Config);
value([start_array|Tokens], Handler, Stack, Config) ->
array(Tokens, handle_event(start_array, Handler, Config), [array|Stack], Config);
value([{literal, Literal}|Tokens], Handler, Stack, Config) when Literal == true; Literal == false; Literal == null ->
maybe_done(Tokens, handle_event({literal, Literal}, Handler, Config), Stack, Config);
value([Literal|Tokens], Handler, Stack, Config) when Literal == true; Literal == false; Literal == null ->
value([{literal, Literal}] ++ Tokens, Handler, Stack, Config);
value([{integer, Number}|Tokens], Handler, Stack, Config) when is_integer(Number) ->
maybe_done(Tokens, handle_event({integer, Number}, Handler, Config), Stack, Config);
value([{float, Number}|Tokens], Handler, Stack, Config) when is_float(Number) ->
maybe_done(Tokens, handle_event({float, Number}, Handler, Config), Stack, Config);
value([{number, Number}|Tokens], Handler, Stack, Config) when is_integer(Number) ->
value([{integer, Number}] ++ Tokens, Handler, Stack, Config);
value([{number, Number}|Tokens], Handler, Stack, Config) when is_float(Number) ->
value([{float, Number}] ++ Tokens, Handler, Stack, Config);
value([Number|Tokens], Handler, Stack, Config) when is_integer(Number) ->
value([{integer, Number}] ++ Tokens, Handler, Stack, Config);
value([Number|Tokens], Handler, Stack, Config) when is_float(Number) ->
value([{float, Number}] ++ Tokens, Handler, Stack, Config);
value([{string, String}|Tokens], Handler, Stack, Config) when is_binary(String) ->
value([String|Tokens], Handler, Stack, Config) when is_binary(String) ->
try clean_string(String, Config) of Clean ->
maybe_done(Tokens, handle_event({string, Clean}, Handler, Config), Stack, Config)
catch error:badarg ->
?error(value, [{string, String}|Tokens], Handler, Stack, Config)
end;
value([String|Tokens], Handler, Stack, Config) when is_binary(String) ->
value([{string, String}] ++ Tokens, Handler, Stack, Config);
value([String|Tokens], Handler, Stack, Config) when is_atom(String) ->
value([{string, atom_to_binary(String, utf8)}] ++ Tokens, Handler, Stack, Config);
value([true|Tokens], Handler, Stack, Config) ->
maybe_done(Tokens, handle_event({literal, true}, Handler, Config), Stack, Config);
value([false|Tokens], Handler, Stack, Config) ->
maybe_done(Tokens, handle_event({literal, false}, Handler, Config), Stack, Config);
value([null|Tokens], Handler, Stack, Config) ->
maybe_done(Tokens, handle_event({literal, null}, Handler, Config), Stack, Config);
value([start_object|Tokens], Handler, Stack, Config) ->
object(Tokens, handle_event(start_object, Handler, Config), [object|Stack], Config);
value([start_array|Tokens], Handler, Stack, Config) ->
array(Tokens, handle_event(start_array, Handler, Config), [array|Stack], Config);
value([Number|Tokens], Handler, Stack, Config) when is_integer(Number) ->
maybe_done(Tokens, handle_event({integer, Number}, Handler, Config), Stack, Config);
value([Number|Tokens], Handler, Stack, Config) when is_float(Number) ->
maybe_done(Tokens, handle_event({float, Number}, Handler, Config), Stack, Config);
value([{raw, Raw}|Tokens], Handler, Stack, Config) when is_binary(Raw) ->
value((jsx:decoder(?MODULE, [], []))(Raw) ++ Tokens, Handler, Stack, Config);
value([{{Year, Month, Day}, {Hour, Min, Sec}}|Tokens], Handler, Stack, Config)
@ -129,6 +119,10 @@ when is_integer(Year), is_integer(Month), is_integer(Day), is_integer(Hour), is_
Stack,
Config
);
value([{_, Value}|Tokens], Handler, Stack, Config) ->
value([Value] ++ Tokens, Handler, Stack, Config);
value([String|Tokens], Handler, Stack, Config) when is_atom(String) ->
value([{string, atom_to_binary(String, utf8)}] ++ Tokens, Handler, Stack, Config);
value([], Handler, Stack, Config) ->
incomplete(value, Handler, Stack, Config);
value(BadTokens, Handler, Stack, Config) when is_list(BadTokens) ->
@ -136,35 +130,19 @@ value(BadTokens, Handler, Stack, Config) when is_list(BadTokens) ->
value(Token, Handler, Stack, Config) ->
value([Token], Handler, Stack, Config).
object([end_object|Tokens], Handler, [{object, _}|Stack], Config) ->
object([end_object|Tokens], Handler, [object|Stack], Config) ->
maybe_done(Tokens, handle_event(end_object, Handler, Config), Stack, Config);
object([{key, Key}|Tokens], Handler, Stack, Config)
when is_atom(Key); is_binary(Key); is_integer(Key) ->
object([Key|Tokens], Handler, Stack, Config);
object([Key|Tokens], Handler, [{object, _Keys}|Stack], Config=#config{repeat_keys=true})
object([Key|Tokens], Handler, [object|Stack], Config)
when is_atom(Key); is_binary(Key); is_integer(Key) ->
try clean_string(fix_key(Key), Config)
of K ->
value(
Tokens,
handle_event({key, K}, Handler, Config),
[{object, []}|Stack],
Config
)
catch error:badarg ->
?error(object, [{string, Key}|Tokens], Handler, Stack, Config)
end;
object([Key|Tokens], Handler, [{object, Keys}|Stack], Config)
when is_atom(Key); is_binary(Key); is_integer(Key) ->
try
CleanKey = clean_string(fix_key(Key), Config),
case sets:is_element(CleanKey, Keys) of true -> erlang:error(badarg); _ -> ok end,
CleanKey
of K ->
value(
Tokens,
handle_event({key, K}, Handler, Config),
[{object, sets:add_element(K, Keys)}|Stack],
[object|Stack],
Config
)
catch error:badarg ->
@ -186,7 +164,7 @@ array(Token, Handler, Stack, Config) ->
maybe_done([end_json], Handler, [], Config) ->
done([end_json], Handler, [], Config);
maybe_done(Tokens, Handler, [{object, _}|_] = Stack, Config) when is_list(Tokens) ->
maybe_done(Tokens, Handler, [object|_] = Stack, Config) when is_list(Tokens) ->
object(Tokens, Handler, Stack, Config);
maybe_done(Tokens, Handler, [array|_] = Stack, Config) when is_list(Tokens) ->
array(Tokens, Handler, Stack, Config);
@ -219,35 +197,35 @@ clean_string(Bin, Config) -> clean(Bin, [], Config).
clean(<<>>, Acc, _) -> iolist_to_binary(Acc);
clean(<<X/utf8, Rest/binary>>, Acc, Config) when X < 16#20 ->
maybe_replace(X, Rest, Acc, Config);
clean(Rest, [Acc, maybe_replace(X, Config)], Config);
clean(<<34, Rest/binary>>, Acc, Config) ->
maybe_replace(34, Rest, Acc, Config);
clean(Rest, [Acc, maybe_replace(34, Config)], Config);
clean(<<47, Rest/binary>>, Acc, Config) ->
maybe_replace(47, Rest, Acc, Config);
clean(Rest, [Acc, maybe_replace(47, Config)], Config);
clean(<<92, Rest/binary>>, Acc, Config) ->
maybe_replace(92, Rest, Acc, Config);
clean(Rest, [Acc, maybe_replace(92, Config)], Config);
clean(<<X/utf8, Rest/binary>>, Acc, Config=#config{uescape=true}) when X >= 16#80 ->
maybe_replace(X, Rest, Acc, Config);
clean(Rest, [Acc, json_escape_sequence(X)], Config);
clean(<<X/utf8, Rest/binary>>, Acc, Config) when X == 16#2028; X == 16#2029 ->
maybe_replace(X, Rest, Acc, Config);
clean(Rest, [Acc, maybe_replace(X, Config)], Config);
clean(<<_/utf8, _/binary>> = Bin, Acc, Config) ->
Size = count(Bin, 0, Config),
<<Clean:Size/binary, Rest/binary>> = Bin,
clean(Rest, [Acc, Clean], Config);
%% surrogates
clean(<<237, X, _, Rest/binary>>, Acc, Config) when X >= 160 ->
maybe_replace(surrogate, Rest, Acc, Config);
clean(Rest, [Acc, maybe_replace(surrogate, Config)], Config);
%% overlong encodings and missing continuations of a 2 byte sequence
clean(<<X, Rest/binary>>, Acc, Config) when X >= 192, X =< 223 ->
maybe_replace(badutf, strip_continuations(Rest, 1), Acc, Config);
clean(strip_continuations(Rest, 1), [Acc, maybe_replace(badutf, Config)], Config);
%% overlong encodings and missing continuations of a 3 byte sequence
clean(<<X, Rest/binary>>, Acc, Config) when X >= 224, X =< 239 ->
maybe_replace(badutf, strip_continuations(Rest, 2), Acc, Config);
clean(strip_continuations(Rest, 2), [Acc, maybe_replace(badutf, Config)], Config);
%% overlong encodings and missing continuations of a 4 byte sequence
clean(<<X, Rest/binary>>, Acc, Config) when X >= 240, X =< 247 ->
maybe_replace(badutf, strip_continuations(Rest, 3), Acc, Config);
clean(strip_continuations(Rest, 3), [Acc, maybe_replace(badutf, Config)], Config);
clean(<<_, Rest/binary>>, Acc, Config) ->
maybe_replace(badutf, Rest, Acc, Config).
clean(Rest, [Acc, maybe_replace(badutf, Config)], Config).
count(<<>>, N, _) -> N;
@ -473,13 +451,16 @@ count(<<126, Rest/binary>>, N, Config) ->
count(<<127, Rest/binary>>, N, Config) ->
count(Rest, N + 1, Config);
count(<<_/utf8, _/binary>>, N, #config{uescape=true}) -> N;
count(<<X/utf8, _/binary>>, N, _) when X == 16#2028; X == 16#2029 -> N;
count(<<X/utf8, Rest/binary>>, N, Config) when X < 16#800 ->
count(Rest, N + 2, Config);
count(<<X/utf8, Rest/binary>>, N, Config) when X < 16#10000 ->
count(Rest, N + 3, Config);
count(<<_/utf8, Rest/binary>>, N, Config) ->
count(Rest, N + 4, Config);
%% u+2028
count(<<226, 128, 168, _/binary>>, N, _) -> N;
%% u+2029
count(<<226, 128, 169, _/binary>>, N, _) -> N;
count(<<X/utf8, Rest/binary>>, N, Config) ->
case X of
X when X < 16#800 -> count(Rest, N + 2, Config);
X when X < 16#10000 -> count(Rest, N + 3, Config);
_ -> count(Rest, N + 4, Config)
end;
count(<<_, _/binary>>, N, _) -> N.
@ -490,53 +471,43 @@ strip_continuations(<<X, Rest/binary>>, N) when X >= 128, X =< 191 ->
strip_continuations(Bin, _) -> Bin.
maybe_replace($\b, Rest, Acc, Config=#config{escaped_strings=true}) ->
clean(Rest, [Acc, $\\, $b], Config);
maybe_replace($\t, Rest, Acc, Config=#config{escaped_strings=true}) ->
clean(Rest, [Acc, $\\, $t], Config);
maybe_replace($\n, Rest, Acc, Config=#config{escaped_strings=true}) ->
clean(Rest, [Acc, $\\, $n], Config);
maybe_replace($\f, Rest, Acc, Config=#config{escaped_strings=true}) ->
clean(Rest, [Acc, $\\, $f], Config);
maybe_replace($\r, Rest, Acc, Config=#config{escaped_strings=true}) ->
clean(Rest, [Acc, $\\, $r], Config);
maybe_replace($\", Rest, Acc, Config=#config{escaped_strings=true}) ->
clean(Rest, [Acc, $\\, $\"], Config);
maybe_replace($/, Rest, Acc, Config=#config{escaped_strings=true}) ->
maybe_replace($\b, #config{escaped_strings=true}) -> <<$\\, $b>>;
maybe_replace($\t, #config{escaped_strings=true}) -> <<$\\, $t>>;
maybe_replace($\n, #config{escaped_strings=true}) -> <<$\\, $n>>;
maybe_replace($\f, #config{escaped_strings=true}) -> <<$\\, $f>>;
maybe_replace($\r, #config{escaped_strings=true}) -> <<$\\, $r>>;
maybe_replace($\", #config{escaped_strings=true}) -> <<$\\, $\">>;
maybe_replace($/, Config=#config{escaped_strings=true}) ->
case Config#config.escaped_forward_slashes of
true -> clean(Rest, [Acc, $\\, $/], Config);
false -> clean(Rest, [Acc, $/], Config)
true -> <<$\\, $/>>;
false -> <<$/>>
end;
maybe_replace($\\, Rest, Acc, Config=#config{escaped_strings=true}) ->
clean(Rest, [Acc, $\\, $\\], Config);
maybe_replace(X, Rest, Acc, Config=#config{escaped_strings=true}) when X < 32 ->
clean(Rest, [Acc, json_escape_sequence(X)], Config);
%% escaped even if no other escaping was requested!
maybe_replace(X, Rest, Acc, Config=#config{uescape=true}) when X >= 16#80 ->
clean(Rest, [Acc, json_escape_sequence(X)], Config);
maybe_replace(X, Rest, Acc, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 ->
maybe_replace($\\, #config{escaped_strings=true}) -> <<$\\, $\\>>;
maybe_replace(X, #config{escaped_strings=true}) when X < 32 ->
json_escape_sequence(X);
maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 ->
case Config#config.unescaped_jsonp of
true -> clean(Rest, [Acc, <<X/utf8>>], Config);
false -> clean(Rest, [Acc, json_escape_sequence(X)], Config)
true -> <<X/utf8>>;
false -> json_escape_sequence(X)
end;
maybe_replace(Atom, _, _, #config{strict_utf8=true}) when is_atom(Atom) ->
maybe_replace(Atom, #config{strict_utf8=true}) when is_atom(Atom) ->
erlang:error(badarg);
maybe_replace(surrogate, Rest, Acc, Config) ->
clean(Rest, [Acc, <<16#fffd/utf8>>], Config);
maybe_replace(badutf, Rest, Acc, Config) ->
clean(Rest, [Acc, <<16#fffd/utf8>>], Config);
maybe_replace(X, Rest, Acc, Config) ->
clean(Rest, [Acc, <<X/utf8>>], Config).
maybe_replace(surrogate, _Config) ->
<<16#fffd/utf8>>;
maybe_replace(badutf, _Config) ->
<<16#fffd/utf8>>;
maybe_replace(X, _Config) ->
<<X/utf8>>.
%% convert a codepoint to it's \uXXXX equiv.
json_escape_sequence(X) when X < 65536 ->
<<A:4, B:4, C:4, D:4>> = <<X:16>>,
[$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))];
<<$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))>>;
json_escape_sequence(X) ->
Adjusted = X - 16#10000,
<<A:10, B:10>> = <<Adjusted:20>>,
json_escape_sequence(A + 16#d800) ++ json_escape_sequence(B + 16#dc00).
[json_escape_sequence(A + 16#d800), json_escape_sequence(B + 16#dc00)].
to_hex(10) -> $a;
@ -1052,11 +1023,12 @@ bad_utf8_test_() ->
json_escape_sequence_test_() ->
[
{"json escape sequence test - 16#0000", ?_assertEqual(json_escape_sequence(16#0000), "\\u0000")},
{"json escape sequence test - 16#abc", ?_assertEqual(json_escape_sequence(16#abc), "\\u0abc")},
{"json escape sequence test - 16#def", ?_assertEqual(json_escape_sequence(16#def), "\\u0def")}
{"json escape sequence test - 16#0000", ?_assertEqual(<<"\\u0000"/utf8>>, json_escape_sequence(16#0000))},
{"json escape sequence test - 16#abc", ?_assertEqual(<<"\\u0abc"/utf8>>, json_escape_sequence(16#abc))},
{"json escape sequence test - 16#def", ?_assertEqual(<<"\\u0def"/utf8>>, json_escape_sequence(16#def))}
].
uescape_test_() ->
[
{"\"\\u0080\"", ?_assertEqual(
@ -1080,6 +1052,7 @@ uescape_test_() ->
)}
].
fix_key_test_() ->
[
{"binary key", ?_assertEqual(fix_key(<<"foo">>), <<"foo">>)},
@ -1088,16 +1061,6 @@ fix_key_test_() ->
].
repeated_key_test_() ->
Parse = fun(Events, Config) -> (parser(?MODULE, [], Config))(Events ++ [end_json]) end,
[
{"repeated key", ?_assertError(
badarg,
Parse([start_object, <<"key">>, true, <<"key">>, true, end_object], [])
)}
].
datetime_test_() ->
[
{"datetime", ?_assertEqual(

View file

@ -23,10 +23,17 @@
-module(jsx_to_term).
-export([to_term/2]).
-export([to_term/2, flatify/1]).
-export([init/1, handle_event/2]).
-export([start_term/0, start_term/1]).
-export([start_object/1, start_array/1, finish/1, insert/2, insert/3, get_key/1, get_value/1]).
-export([
start_term/1,
start_object/1,
start_array/1,
finish/1,
insert/2,
get_key/1,
get_value/1
]).
-record(config, {
@ -37,15 +44,27 @@
-type config() :: list().
-export_type([config/0]).
-type json_value() :: list({binary(), json_value()})
| list(json_value())
-ifndef(maps_support).
-type json_value() :: list(json_value())
| list({binary() | atom(), json_value()})
| true
| false
| null
| integer()
| float()
| binary().
-endif.
-ifdef(maps_support).
-type json_value() :: list(json_value())
| map()
| true
| false
| null
| integer()
| float()
| binary().
-endif.
-spec to_term(Source::binary(), Config::config()) -> json_value().
@ -79,10 +98,11 @@ parse_config([K|Rest] = Options, Config) ->
parse_config([], Config) ->
Config.
-type state() :: {[any()], #config{}}.
-type state() :: {list(), #config{}}.
-spec init(Config::proplists:proplist()) -> state().
init(Config) -> {[], parse_config(Config)}.
init(Config) -> start_term(Config).
-spec handle_event(Event::any(), State::state()) -> state().
@ -118,47 +138,46 @@ format_key(Key, Config) ->
%% the stack is a list of in progress objects/arrays
%% `[Current, Parent, Grandparent,...OriginalAncestor]`
%% an object has the representation on the stack of
%% `{object, [{NthKey, NthValue}, {NMinus1Key, NthMinus1Value},...{FirstKey, FirstValue}]}`
%% of if there's a key with a yet to be matched value
%% `{object, Key, [{NthKey, NthValue},...]}`
%% `{object, [
%% {NthKey, NthValue},
%% {NMinus1Key, NthMinus1Value},
%% ...,
%% {FirstKey, FirstValue}
%% ]}`
%% or if returning maps
%% `{object, #{
%% FirstKey => FirstValue,
%% SecondKey => SecondValue,
%% ...,
%% NthKey => NthValue
%% }}`
%% or if there's a key with a yet to be matched value
%% `{object, Key, ...}`
%% an array looks like
%% `{array, [NthValue, NthMinus1Value,...FirstValue]}`
start_term() -> {[], #config{}}.
start_term(Config) when is_list(Config) -> {[], parse_config(Config)}.
-ifndef(maps_support).
%% allocate a new object on top of the stack
start_object({Stack, Config}) -> {[{object, []}] ++ Stack, Config}.
%% allocate a new array on top of the stack
start_array({Stack, Config}) -> {[{array, []}] ++ Stack, Config}.
-ifndef(maps_support).
finish(Any) -> finish0(Any).
-endif.
-ifdef(maps_support).
finish({[{object, []}], Config=#config{return_maps=true}}) ->
{#{}, Config};
finish({[{object, []}|Rest], Config=#config{return_maps=true}}) ->
insert(#{}, {Rest, Config});
finish({[{object, Pairs}], Config=#config{return_maps=true}}) ->
{maps:from_list(Pairs), Config};
finish({[{object, Pairs}|Rest], Config=#config{return_maps=true}}) ->
insert(maps:from_list(Pairs), {Rest, Config});
finish(Else) -> finish0(Else).
-endif.
%% finish an object or array and insert it into the parent object if it exists or
%% return it if it is the root object
finish0({[{object, []}], Config}) -> {[{}], Config};
finish0({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
finish0({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
finish0({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
finish0({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
finish0({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
finish0(_) -> erlang:error(badarg).
%% return it if it is the root object
finish({[{object, []}], Config}) -> {[{}], Config};
finish({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
finish({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
finish({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
finish({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
finish({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
finish(_) -> erlang:error(badarg).
%% insert a value when there's no parent object or array
insert(Value, {[], Config}) -> {Value, Config};
@ -170,11 +189,49 @@ insert(Value, {[{object, Key, Pairs}|Rest], Config}) ->
insert(Value, {[{array, Values}|Rest], Config}) ->
{[{array, [Value] ++ Values}] ++ Rest, Config};
insert(_, _) -> erlang:error(badarg).
-endif.
%% insert a key/value pair into an object
insert(Key, Value, {[{object, Pairs}|Rest], Config}) ->
-ifdef(maps_support).
%% allocate a new object on top of the stack
start_object({Stack, Config=#config{return_maps=true}}) ->
{[{object, #{}}] ++ Stack, Config};
start_object({Stack, Config}) ->
{[{object, []}] ++ Stack, Config}.
%% allocate a new array on top of the stack
start_array({Stack, Config}) -> {[{array, []}] ++ Stack, Config}.
%% finish an object or array and insert it into the parent object if it exists or
%% return it if it is the root object
finish({[{object, Map}], Config=#config{return_maps=true}}) -> {Map, Config};
finish({[{object, Map}|Rest], Config=#config{return_maps=true}}) -> insert(Map, {Rest, Config});
finish({[{object, []}], Config}) -> {[{}], Config};
finish({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
finish({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
finish({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
finish({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
finish({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
finish(_) -> erlang:error(badarg).
%% insert a value when there's no parent object or array
insert(Value, {[], Config}) -> {Value, Config};
%% insert a key or value into an object or array, autodetects the 'right' thing
insert(Key, {[{object, Map}|Rest], Config=#config{return_maps=true}}) ->
{[{object, Key, Map}] ++ Rest, Config};
insert(Key, {[{object, Pairs}|Rest], Config}) ->
{[{object, Key, Pairs}] ++ Rest, Config};
insert(Value, {[{object, Key, Map}|Rest], Config=#config{return_maps=true}}) ->
{[{object, maps:put(Key, Value, Map)}] ++ Rest, Config};
insert(Value, {[{object, Key, Pairs}|Rest], Config}) ->
{[{object, [{Key, Value}] ++ Pairs}] ++ Rest, Config};
insert(_, _, _) -> erlang:error(badarg).
insert(Value, {[{array, Values}|Rest], Config}) ->
{[{array, [Value] ++ Values}] ++ Rest, Config};
insert(_, _) -> erlang:error(badarg).
-endif.
get_key({[{object, Key, _}|_], _}) -> Key;
@ -185,6 +242,15 @@ get_value({Value, _Config}) -> Value;
get_value(_) -> erlang:error(badarg).
%% we know the structure of our accumulator so we can safely
%% flatten like this
flatify(List) -> flatify(List, []).
%% head of list should always be []
flatify([], Tail) -> Tail;
flatify([H, T], Tail) -> flatify(H, [T] ++ Tail).
%% eunit tests
-ifdef(TEST).
@ -235,10 +301,6 @@ format_key_test_() ->
rep_manipulation_test_() ->
[
{"allocate a new context", ?_assertEqual(
{[], #config{}},
start_term()
)},
{"allocate a new context with option", ?_assertEqual(
{[], #config{labels=atom}},
start_term([{labels, atom}])
@ -283,10 +345,6 @@ rep_manipulation_test_() ->
{[{array, [value]}, junk], #config{}},
insert(value, {[{array, []}, junk], #config{}})
)},
{"insert a key/value pair into an object", ?_assertEqual(
{[{object, [{key, value}, {x, y}]}, junk], #config{}},
insert(key, value, {[{object, [{x, y}]}, junk], #config{}})
)},
{"finish an object with no ancestor", ?_assertEqual(
{[{a, b}, {x, y}], #config{}},
finish({[{object, [{x, y}, {a, b}]}], #config{}})
@ -309,7 +367,54 @@ rep_manipulation_test_() ->
)}
].
-ifdef(maps_support).
rep_manipulation_with_maps_test_() ->
[
{"allocate a new object on an empty stack", ?_assertEqual(
{[{object, #{}}], #config{return_maps=true}},
start_object({[], #config{return_maps=true}})
)},
{"allocate a new object on a stack", ?_assertEqual(
{[{object, #{}}, {object, #{}}], #config{return_maps=true}},
start_object({[{object, #{}}], #config{return_maps=true}})
)},
{"insert a key into an object", ?_assertEqual(
{[{object, key, #{}}, junk], #config{return_maps=true}},
insert(key, {[{object, #{}}, junk], #config{return_maps=true}})
)},
{"get current key", ?_assertEqual(
key,
get_key({[{object, key, #{}}], #config{return_maps=true}})
)},
{"try to get non-key from object", ?_assertError(
badarg,
get_key({[{object, #{}}], #config{return_maps=true}})
)},
{"insert a value into an object", ?_assertEqual(
{[{object, #{key => value}}, junk], #config{return_maps=true}},
insert(value, {[{object, key, #{}}, junk], #config{return_maps=true}})
)},
{"finish an object with no ancestor", ?_assertEqual(
{#{a => b, x => y}, #config{return_maps=true}},
finish({[{object, #{x => y, a => b}}], #config{return_maps=true}})
)},
{"finish an empty object", ?_assertEqual(
{#{}, #config{return_maps=true}},
finish({[{object, #{}}], #config{return_maps=true}})
)},
{"finish an object with an ancestor", ?_assertEqual(
{
[{object, #{key => #{a => b, x => y}, foo => bar}}],
#config{return_maps=true}
},
finish({
[{object, #{x => y, a => b}}, {object, key, #{foo => bar}}],
#config{return_maps=true}
})
)}
].
return_maps_test_() ->
[
@ -334,7 +439,6 @@ return_maps_test_() ->
jsx:decode(<<"[{}]">>, [return_maps])
)}
].
-endif.

View file

@ -27,15 +27,7 @@
-export([init/1, handle_event/2]).
-record(config, {
repeated_keys = true
}).
-type config() :: [].
-export_type([config/0]).
-spec is_json(Source::binary(), Config::config()) -> true | false | {incomplete, jsx:decoder()}.
-spec is_json(Source::binary(), Config::jsx_config:config()) -> true | false | {incomplete, jsx:decoder()}.
is_json(Source, Config) when is_list(Config) ->
try (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
@ -43,7 +35,7 @@ is_json(Source, Config) when is_list(Config) ->
end.
-spec is_term(Source::any(), Config::config()) -> true | false | {incomplete, jsx:encoder()}.
-spec is_term(Source::any(), Config::jsx_config:config()) -> true | false | {incomplete, jsx:encoder()}.
is_term(Source, Config) when is_list(Config) ->
try (jsx:encoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
@ -51,15 +43,15 @@ is_term(Source, Config) when is_list(Config) ->
end.
parse_config(Config) -> parse_config(Config, #config{}).
parse_config(Config) -> parse_config(Config, []).
%% ignore deprecated flags
parse_config([no_repeated_keys|Rest], Config) ->
parse_config(Rest, Config#config{repeated_keys=false});
%% deprecated, use `no_repeated_keys`
parse_config(Rest, Config);
parse_config([{repeated_keys, Val}|Rest], Config) when Val == true; Val == false ->
parse_config(Rest, Config#config{repeated_keys=Val});
parse_config(Rest, Config);
parse_config([repeated_keys|Rest], Config) ->
parse_config(Rest, Config#config{repeated_keys=true});
parse_config(Rest, Config);
parse_config([{K, _}|Rest] = Options, Config) ->
case lists:member(K, jsx_config:valid_flags()) of
true -> parse_config(Rest, Config);
@ -73,27 +65,18 @@ parse_config([K|Rest] = Options, Config) ->
parse_config([], Config) ->
Config.
-type state() :: {#config{}, any()}.
%% we don't actually need any state for this
-type state() :: [].
-spec init(Config::proplists:proplist()) -> state().
init(Config) -> {parse_config(Config), []}.
init(Config) -> parse_config(Config).
-spec handle_event(Event::any(), State::state()) -> state().
handle_event(end_json, _) -> true;
handle_event(_, {Config, _} = State) when Config#config.repeated_keys == true -> State;
handle_event(start_object, {Config, Keys}) -> {Config, [dict:new()] ++ Keys};
handle_event(end_object, {Config, [_|Keys]}) -> {Config, Keys};
handle_event({key, Key}, {Config, [CurrentKeys|Keys]}) ->
case dict:is_key(Key, CurrentKeys) of
true -> erlang:error(badarg);
false -> {Config, [dict:store(Key, blah, CurrentKeys)|Keys]}
end;
handle_event(_, State) -> State.
@ -105,15 +88,15 @@ handle_event(_, State) -> State.
config_test_() ->
[
{"empty config", ?_assertEqual(#config{}, parse_config([]))},
{"no repeat keys", ?_assertEqual(#config{repeated_keys=false}, parse_config([no_repeated_keys]))},
{"bare repeated keys", ?_assertEqual(#config{}, parse_config([repeated_keys]))},
{"empty config", ?_assertEqual([], parse_config([]))},
{"no repeat keys", ?_assertEqual([], parse_config([no_repeated_keys]))},
{"bare repeated keys", ?_assertEqual([], parse_config([repeated_keys]))},
{"repeated keys true", ?_assertEqual(
#config{},
[],
parse_config([{repeated_keys, true}])
)},
{"repeated keys false", ?_assertEqual(
#config{repeated_keys=false},
[],
parse_config([{repeated_keys, false}])
)},
{"invalid opt flag", ?_assertError(badarg, parse_config([error]))},
@ -121,50 +104,13 @@ config_test_() ->
].
repeated_keys_test_() ->
RepeatedKey = [
start_object,
{key, <<"alpha">>},
{literal, true},
{key, <<"alpha">>},
{literal, false},
end_object,
end_json
],
NestedKey = [
start_object,
{key, <<"alpha">>},
start_object,
{key, <<"alpha">>},
start_object,
{key, <<"alpha">>},
{literal, true},
end_object,
end_object,
end_object,
end_json
],
[
{"repeated key", ?_assert(
lists:foldl(fun handle_event/2, {#config{}, []}, RepeatedKey)
)},
{"no repeated key", ?_assertError(
badarg,
lists:foldl(fun handle_event/2, {#config{repeated_keys=false}, []}, RepeatedKey)
)},
{"nested key", ?_assert(
lists:foldl(fun handle_event/2, {#config{repeated_keys=false}, []}, NestedKey)
)}
].
handle_event_test_() ->
Data = jsx:test_cases() ++ jsx:special_test_cases(),
[
{
Title, ?_assertEqual(
true,
lists:foldl(fun handle_event/2, {#config{}, []}, Events ++ [end_json])
lists:foldl(fun handle_event/2, [], Events ++ [end_json])
)
} || {Title, _, _, Events} <- Data
].