reworks is_json to be more lenient, optionally more strict

This commit is contained in:
alisdair sullivan 2011-07-05 21:42:08 -07:00
parent 51076f2054
commit f3aa254664
2 changed files with 82 additions and 27 deletions

View file

@ -24,10 +24,10 @@
-define(is_utf_encoding(X), -define(is_utf_encoding(X),
X == utf8 X == utf8
; X == utf16 ; X == utf16
; X == utf32 ; X == utf32
; X == {utf16, little} ; X == {utf16, little}
; X == {utf32, little} ; X == {utf32, little}
). ).
@ -35,11 +35,11 @@
-type jsx_opt() :: {escaped_unicode, ascii | codepoint | none} -type jsx_opt() :: {escaped_unicode, ascii | codepoint | none}
| {multi_term, true | false} | {multi_term, true | false}
| {encoding, auto | {encoding, auto
| utf8 | utf8
| utf16 | utf16
| {utf16, little} | {utf16, little}
| utf32 | utf32
| {utf32, little} | {utf32, little}
}. }.
@ -123,7 +123,9 @@
-type verify_opts() :: [verify_opt()]. -type verify_opts() :: [verify_opt()].
-type verify_opt() :: {encoding, auto | supported_utf()}. -type verify_opt() :: {encoding, auto | supported_utf()}
| {repeated_keys, true | false}
| {naked_values, true | false}.
-type format_opts() :: [format_opt()]. -type format_opts() :: [format_opt()].

View file

@ -29,6 +29,7 @@
-include("jsx_common.hrl"). -include("jsx_common.hrl").
-include("jsx_verify.hrl").
@ -38,7 +39,17 @@
is_json(JSON, OptsList) when is_binary(JSON) -> is_json(JSON, OptsList) when is_binary(JSON) ->
P = jsx:decoder(extract_parser_opts(OptsList)), P = jsx:decoder(extract_parser_opts(OptsList)),
is_json(fun() -> P(JSON) end, OptsList); is_json(fun() -> P(JSON) end, OptsList);
is_json(F, _OptsList) when is_function(F) -> collect(F(), [[]]). is_json(F, OptsList) when is_function(F) ->
Opts = parse_opts(OptsList, #verify_opts{}),
case Opts#verify_opts.naked_values of
true -> collect(F(), Opts, [[]])
; false ->
case F() of
{event, start_object, Next} -> collect(Next(), Opts, [[]])
; {event, start_array, Next} -> collect(Next(), Opts, [[]])
; _ -> false
end
end.
extract_parser_opts(Opts) -> extract_parser_opts(Opts) ->
@ -57,36 +68,58 @@ extract_parser_opts([K|Rest], Acc) ->
end. end.
parse_opts([{repeated_keys, Val}|Rest], Opts) ->
parse_opts(Rest, Opts#verify_opts{repeated_keys = Val});
parse_opts([repeated_keys|Rest], Opts) ->
parse_opts(Rest, Opts#verify_opts{repeated_keys = true});
parse_opts([{naked_values, Val}|Rest], Opts) ->
parse_opts(Rest, Opts#verify_opts{naked_values = Val});
parse_opts([naked_values|Rest], Opts) ->
parse_opts(Rest, Opts#verify_opts{naked_values = true});
parse_opts([_|Rest], Opts) ->
parse_opts(Rest, Opts);
parse_opts([], Opts) ->
Opts.
collect({event, end_json, _Next}, _Keys) ->
collect({event, end_json, _Next}, _Opts, _Keys) ->
true; true;
%% allocate new key accumulator at start_object, discard it at end_object %% allocate new key accumulator at start_object, discard it at end_object
collect({event, start_object, Next}, Keys) -> collect(Next(), [[]|Keys]); collect({event, start_object, Next},
collect({event, end_object, Next}, [_|Keys]) -> collect(Next(), [Keys]); Opts = #verify_opts{repeated_keys = false},
Keys) ->
collect(Next(), Opts, [[]|Keys]);
collect({event, end_object, Next},
Opts = #verify_opts{repeated_keys = false},
[_|Keys]) ->
collect(Next(), Opts, [Keys]);
%% check to see if key has already been encountered, if not add it to the key %% check to see if key has already been encountered, if not add it to the key
%% accumulator and continue, else return false %% accumulator and continue, else return false
collect({event, {key, Key}, Next}, [Current|Keys]) -> collect({event, {key, Key}, Next},
Opts = #verify_opts{repeated_keys = false},
[Current|Keys]) ->
case lists:member(Key, Current) of case lists:member(Key, Current) of
true -> false true -> false
; false -> collect(Next(), [[Key] ++ Current] ++ Keys) ; false -> collect(Next(), Opts, [[Key] ++ Current] ++ Keys)
end; end;
collect({event, _, Next}, Keys) -> collect({event, _, Next}, Opts, Keys) ->
collect(Next(), Keys); collect(Next(), Opts, Keys);
%% needed to parse numbers that don't have trailing whitespace in less strict %% needed to parse numbers that don't have trailing whitespace in less strict
%% mode %% mode
collect({incomplete, More}, Keys) -> collect({incomplete, More}, Opts, Keys) ->
collect(More(end_stream), Keys); collect(More(end_stream), Opts, Keys);
collect(_, _) -> collect(_, _, _) ->
false. false.
@ -144,13 +177,24 @@ false_test_() ->
{"unbalanced list", ?_assert(is_json(<<"[[[]]">>, []) =:= false)}, {"unbalanced list", ?_assert(is_json(<<"[[[]]">>, []) =:= false)},
{"trailing comma", {"trailing comma",
?_assert(is_json(<<"[ true, false, null, ]">>, []) =:= false) ?_assert(is_json(<<"[ true, false, null, ]">>, []) =:= false)
}, }
{"repeated key", ].
repeated_keys_test_() ->
[
{"repeated key forbidden",
?_assert(is_json( ?_assert(is_json(
<<"{\"key\": true, \"key\": true}">>, <<"{\"key\": true, \"key\": true}">>,
[] [{repeated_keys, false}]
) =:= false ) =:= false
) )
},
{"repeated key allowed",
?_assert(is_json(
<<"{\"key\": true, \"key\": true}">>,
[{repeated_keys, true}]
) =:= true
)
} }
]. ].
@ -162,11 +206,20 @@ naked_value_test_() ->
{"naked number", {"naked number",
?_assert(is_json(<<"1">>, []) =:= true) ?_assert(is_json(<<"1">>, []) =:= true)
}, },
{"naked string",
?_assert(is_json(<<"\"i am not json\"">>, []) =:= true)
},
{"naked true",
?_assert(is_json(<<"true">>, [{naked_values, false}]) =:= false)
},
{"naked number",
?_assert(is_json(<<"1">>, [{naked_values, false}]) =:= false)
},
{"naked string", {"naked string",
?_assert(is_json( ?_assert(is_json(
<<"\"i am not json\"">>, <<"\"i am not json\"">>,
[] [{naked_values, false}]
) =:= true ) =:= false
) )
} }
]. ].