strip out checks for repeated keys in generated json. no one cares
about repeated keys
This commit is contained in:
parent
d2a7064e4e
commit
9842a00b4d
5 changed files with 28 additions and 112 deletions
|
@ -387,10 +387,6 @@ additional options beyond these. see
|
||||||
control codes and problematic codepoints and replacing them with the
|
control codes and problematic codepoints and replacing them with the
|
||||||
appropriate escapes
|
appropriate escapes
|
||||||
|
|
||||||
- `repeat_keys`
|
|
||||||
|
|
||||||
this flag circumvents checking for repeated keys in generated json
|
|
||||||
|
|
||||||
- `stream`
|
- `stream`
|
||||||
|
|
||||||
see [incomplete input](#incomplete-input)
|
see [incomplete input](#incomplete-input)
|
||||||
|
|
|
@ -63,12 +63,14 @@ parse_config([unescaped_jsonp|Rest], Config) ->
|
||||||
parse_config(Rest, Config#config{unescaped_jsonp=true});
|
parse_config(Rest, Config#config{unescaped_jsonp=true});
|
||||||
parse_config([dirty_strings|Rest], Config) ->
|
parse_config([dirty_strings|Rest], Config) ->
|
||||||
parse_config(Rest, Config#config{dirty_strings=true});
|
parse_config(Rest, Config#config{dirty_strings=true});
|
||||||
|
%% retained for backwards compat, now does nothing however
|
||||||
parse_config([repeat_keys|Rest], Config) ->
|
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([uescape|Rest], Config) ->
|
||||||
parse_config(Rest, Config#config{uescape=true});
|
parse_config(Rest, Config#config{uescape=true});
|
||||||
parse_config([strict|Rest], Config) ->
|
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_commas=true,
|
||||||
strict_utf8=true,
|
strict_utf8=true,
|
||||||
strict_single_quotes=true,
|
strict_single_quotes=true,
|
||||||
|
@ -190,7 +192,6 @@ config_test_() ->
|
||||||
escaped_strings = true,
|
escaped_strings = true,
|
||||||
unescaped_jsonp = true,
|
unescaped_jsonp = true,
|
||||||
dirty_strings = true,
|
dirty_strings = true,
|
||||||
repeat_keys = true,
|
|
||||||
strict_comments = true,
|
strict_comments = true,
|
||||||
strict_commas = true,
|
strict_commas = true,
|
||||||
strict_utf8 = true,
|
strict_utf8 = true,
|
||||||
|
@ -274,7 +275,6 @@ config_to_list_test_() ->
|
||||||
escaped_strings,
|
escaped_strings,
|
||||||
unescaped_jsonp,
|
unescaped_jsonp,
|
||||||
dirty_strings,
|
dirty_strings,
|
||||||
repeat_keys,
|
|
||||||
stream,
|
stream,
|
||||||
uescape,
|
uescape,
|
||||||
strict
|
strict
|
||||||
|
@ -284,7 +284,6 @@ config_to_list_test_() ->
|
||||||
escaped_strings = true,
|
escaped_strings = true,
|
||||||
unescaped_jsonp = true,
|
unescaped_jsonp = true,
|
||||||
dirty_strings = true,
|
dirty_strings = true,
|
||||||
repeat_keys = true,
|
|
||||||
strict_comments = true,
|
strict_comments = true,
|
||||||
strict_utf8 = true,
|
strict_utf8 = true,
|
||||||
strict_single_quotes = true,
|
strict_single_quotes = true,
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
escaped_strings = false :: boolean(),
|
escaped_strings = false :: boolean(),
|
||||||
unescaped_jsonp = false :: boolean(),
|
unescaped_jsonp = false :: boolean(),
|
||||||
dirty_strings = false :: boolean(),
|
dirty_strings = false :: boolean(),
|
||||||
repeat_keys = false :: boolean(),
|
|
||||||
strict_comments = false :: boolean(),
|
strict_comments = false :: boolean(),
|
||||||
strict_commas = false :: boolean(),
|
strict_commas = false :: boolean(),
|
||||||
strict_utf8 = false :: boolean(),
|
strict_utf8 = false :: boolean(),
|
||||||
|
|
|
@ -88,7 +88,7 @@ handle_event(Event, {Handler, State}, _Config) -> {Handler, Handler:handle_event
|
||||||
|
|
||||||
|
|
||||||
value([start_object|Tokens], Handler, Stack, Config) ->
|
value([start_object|Tokens], Handler, Stack, Config) ->
|
||||||
object(Tokens, handle_event(start_object, Handler, Config), [{object, sets:new()}|Stack], Config);
|
object(Tokens, handle_event(start_object, Handler, Config), [object|Stack], Config);
|
||||||
value([start_array|Tokens], Handler, Stack, Config) ->
|
value([start_array|Tokens], Handler, Stack, Config) ->
|
||||||
array(Tokens, handle_event(start_array, Handler, Config), [array|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 ->
|
value([{literal, Literal}|Tokens], Handler, Stack, Config) when Literal == true; Literal == false; Literal == null ->
|
||||||
|
@ -136,35 +136,19 @@ value(BadTokens, Handler, Stack, Config) when is_list(BadTokens) ->
|
||||||
value(Token, Handler, Stack, Config) ->
|
value(Token, Handler, Stack, Config) ->
|
||||||
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);
|
maybe_done(Tokens, handle_event(end_object, Handler, Config), Stack, Config);
|
||||||
object([{key, Key}|Tokens], Handler, Stack, Config)
|
object([{key, Key}|Tokens], Handler, Stack, Config)
|
||||||
when is_atom(Key); is_binary(Key); is_integer(Key) ->
|
when is_atom(Key); is_binary(Key); is_integer(Key) ->
|
||||||
object([Key|Tokens], Handler, Stack, Config);
|
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) ->
|
when is_atom(Key); is_binary(Key); is_integer(Key) ->
|
||||||
try clean_string(fix_key(Key), Config)
|
try clean_string(fix_key(Key), Config)
|
||||||
of K ->
|
of K ->
|
||||||
value(
|
value(
|
||||||
Tokens,
|
Tokens,
|
||||||
handle_event({key, K}, Handler, Config),
|
handle_event({key, K}, Handler, Config),
|
||||||
[{object, []}|Stack],
|
[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],
|
|
||||||
Config
|
Config
|
||||||
)
|
)
|
||||||
catch error:badarg ->
|
catch error:badarg ->
|
||||||
|
@ -186,7 +170,7 @@ array(Token, Handler, Stack, Config) ->
|
||||||
|
|
||||||
maybe_done([end_json], Handler, [], Config) ->
|
maybe_done([end_json], Handler, [], Config) ->
|
||||||
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);
|
object(Tokens, Handler, Stack, Config);
|
||||||
maybe_done(Tokens, Handler, [array|_] = Stack, Config) when is_list(Tokens) ->
|
maybe_done(Tokens, Handler, [array|_] = Stack, Config) when is_list(Tokens) ->
|
||||||
array(Tokens, Handler, Stack, Config);
|
array(Tokens, Handler, Stack, Config);
|
||||||
|
@ -1057,6 +1041,7 @@ json_escape_sequence_test_() ->
|
||||||
{"json escape sequence test - 16#def", ?_assertEqual(json_escape_sequence(16#def), "\\u0def")}
|
{"json escape sequence test - 16#def", ?_assertEqual(json_escape_sequence(16#def), "\\u0def")}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
|
||||||
uescape_test_() ->
|
uescape_test_() ->
|
||||||
[
|
[
|
||||||
{"\"\\u0080\"", ?_assertEqual(
|
{"\"\\u0080\"", ?_assertEqual(
|
||||||
|
@ -1080,6 +1065,7 @@ uescape_test_() ->
|
||||||
)}
|
)}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
|
||||||
fix_key_test_() ->
|
fix_key_test_() ->
|
||||||
[
|
[
|
||||||
{"binary key", ?_assertEqual(fix_key(<<"foo">>), <<"foo">>)},
|
{"binary key", ?_assertEqual(fix_key(<<"foo">>), <<"foo">>)},
|
||||||
|
@ -1088,16 +1074,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_test_() ->
|
||||||
[
|
[
|
||||||
{"datetime", ?_assertEqual(
|
{"datetime", ?_assertEqual(
|
||||||
|
|
|
@ -27,15 +27,7 @@
|
||||||
-export([init/1, handle_event/2]).
|
-export([init/1, handle_event/2]).
|
||||||
|
|
||||||
|
|
||||||
-record(config, {
|
-spec is_json(Source::binary(), Config::jsx_config:config()) -> true | false | {incomplete, jsx:decoder()}.
|
||||||
repeated_keys = true
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type config() :: [].
|
|
||||||
-export_type([config/0]).
|
|
||||||
|
|
||||||
|
|
||||||
-spec is_json(Source::binary(), Config::config()) -> true | false | {incomplete, jsx:decoder()}.
|
|
||||||
|
|
||||||
is_json(Source, Config) when is_list(Config) ->
|
is_json(Source, Config) when is_list(Config) ->
|
||||||
try (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
|
try (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
|
||||||
|
@ -43,7 +35,7 @@ is_json(Source, Config) when is_list(Config) ->
|
||||||
end.
|
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) ->
|
is_term(Source, Config) when is_list(Config) ->
|
||||||
try (jsx:encoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
|
try (jsx:encoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
|
||||||
|
@ -51,15 +43,15 @@ is_term(Source, Config) when is_list(Config) ->
|
||||||
end.
|
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([no_repeated_keys|Rest], Config) ->
|
||||||
parse_config(Rest, Config#config{repeated_keys=false});
|
parse_config(Rest, Config);
|
||||||
%% deprecated, use `no_repeated_keys`
|
|
||||||
parse_config([{repeated_keys, Val}|Rest], Config) when Val == true; Val == false ->
|
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([repeated_keys|Rest], Config) ->
|
||||||
parse_config(Rest, Config#config{repeated_keys=true});
|
parse_config(Rest, Config);
|
||||||
parse_config([{K, _}|Rest] = Options, Config) ->
|
parse_config([{K, _}|Rest] = Options, Config) ->
|
||||||
case lists:member(K, jsx_config:valid_flags()) of
|
case lists:member(K, jsx_config:valid_flags()) of
|
||||||
true -> parse_config(Rest, Config);
|
true -> parse_config(Rest, Config);
|
||||||
|
@ -73,27 +65,18 @@ parse_config([K|Rest] = Options, Config) ->
|
||||||
parse_config([], Config) ->
|
parse_config([], Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
-type state() :: {#config{}, any()}.
|
|
||||||
|
%% we don't actually need any state for this
|
||||||
|
-type state() :: [].
|
||||||
-spec init(Config::proplists:proplist()) -> 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().
|
-spec handle_event(Event::any(), State::state()) -> state().
|
||||||
|
|
||||||
handle_event(end_json, _) -> true;
|
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.
|
handle_event(_, State) -> State.
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,15 +88,15 @@ handle_event(_, State) -> State.
|
||||||
|
|
||||||
config_test_() ->
|
config_test_() ->
|
||||||
[
|
[
|
||||||
{"empty config", ?_assertEqual(#config{}, parse_config([]))},
|
{"empty config", ?_assertEqual([], parse_config([]))},
|
||||||
{"no repeat keys", ?_assertEqual(#config{repeated_keys=false}, parse_config([no_repeated_keys]))},
|
{"no repeat keys", ?_assertEqual([], parse_config([no_repeated_keys]))},
|
||||||
{"bare repeated keys", ?_assertEqual(#config{}, parse_config([repeated_keys]))},
|
{"bare repeated keys", ?_assertEqual([], parse_config([repeated_keys]))},
|
||||||
{"repeated keys true", ?_assertEqual(
|
{"repeated keys true", ?_assertEqual(
|
||||||
#config{},
|
[],
|
||||||
parse_config([{repeated_keys, true}])
|
parse_config([{repeated_keys, true}])
|
||||||
)},
|
)},
|
||||||
{"repeated keys false", ?_assertEqual(
|
{"repeated keys false", ?_assertEqual(
|
||||||
#config{repeated_keys=false},
|
[],
|
||||||
parse_config([{repeated_keys, false}])
|
parse_config([{repeated_keys, false}])
|
||||||
)},
|
)},
|
||||||
{"invalid opt flag", ?_assertError(badarg, parse_config([error]))},
|
{"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_() ->
|
handle_event_test_() ->
|
||||||
Data = jsx:test_cases() ++ jsx:special_test_cases(),
|
Data = jsx:test_cases() ++ jsx:special_test_cases(),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
Title, ?_assertEqual(
|
Title, ?_assertEqual(
|
||||||
true,
|
true,
|
||||||
lists:foldl(fun handle_event/2, {#config{}, []}, Events ++ [end_json])
|
lists:foldl(fun handle_event/2, [], Events ++ [end_json])
|
||||||
)
|
)
|
||||||
} || {Title, _, _, Events} <- Data
|
} || {Title, _, _, Events} <- Data
|
||||||
].
|
].
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue