allow a single trailing comma in objects or arrays

This commit is contained in:
alisdair sullivan 2014-08-19 17:47:44 -07:00
parent c25bb49902
commit 6b43609730
4 changed files with 56 additions and 0 deletions

View file

@ -133,6 +133,10 @@ number of ways. see the section on `strict` in [options](#option) below though
json has no official comments but this parser allows c/c++ style comments. json has no official comments but this parser allows c/c++ style comments.
anywhere whitespace is allowed you can insert comments (both `// ...` and `/* ... */`) anywhere whitespace is allowed you can insert comments (both `// ...` and `/* ... */`)
some particularly irresponsible json emitters leave trailing commas at the end of
objects or arrays. **jsx** allows a single trailing comma in input. multiple commas
in any posistion or a preceding comma are still errors
all **jsx** decoder input should be `utf8` encoded binaries. sometimes you get binaries all **jsx** decoder input should be `utf8` encoded binaries. sometimes you get binaries
that are almost but not quite valid utf8 whether due to improper escaping or poor that are almost but not quite valid utf8 whether due to improper escaping or poor
encoding. **jsx** replaces invalid codepoints and poorly formed sequences with the encoding. **jsx** replaces invalid codepoints and poorly formed sequences with the
@ -337,6 +341,7 @@ option() = escaped_forward_slashes
| stream | stream
strict_option() = comments strict_option() = comments
| trailing_commas
| utf8 | utf8
| single_quotes | single_quotes
| escapes | escapes
@ -390,6 +395,10 @@ additional options beyond these. see
comments are disabled and result in a `badarg` error comments are disabled and result in a `badarg` error
* `trailing_commas`
trailing commas in an object or list result in `badarg` errors
* `utf8` * `utf8`
invalid codepoints and malformed unicode result in `badarg` errors invalid codepoints and malformed unicode result in `badarg` errors

View file

@ -65,6 +65,7 @@ parse_config([dirty_strings|Rest], Config) ->
parse_config(Rest, Config#config{dirty_strings=true}); parse_config(Rest, Config#config{dirty_strings=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_utf8=true, strict_utf8=true,
strict_single_quotes=true, strict_single_quotes=true,
strict_escapes=true strict_escapes=true
@ -89,6 +90,8 @@ parse_config(_Options, _Config) -> erlang:error(badarg).
parse_strict([], Rest, Config) -> parse_config(Rest, Config); parse_strict([], Rest, Config) -> parse_config(Rest, Config);
parse_strict([comments|Strict], Rest, Config) -> parse_strict([comments|Strict], Rest, Config) ->
parse_strict(Strict, Rest, Config#config{strict_comments=true}); parse_strict(Strict, Rest, Config#config{strict_comments=true});
parse_strict([trailing_commas|Strict], Rest, Config) ->
parse_strict(Strict, Rest, Config#config{strict_commas=true});
parse_strict([utf8|Strict], Rest, Config) -> parse_strict([utf8|Strict], Rest, Config) ->
parse_strict(Strict, Rest, Config#config{strict_utf8=true}); parse_strict(Strict, Rest, Config#config{strict_utf8=true});
parse_strict([single_quotes|Strict], Rest, Config) -> parse_strict([single_quotes|Strict], Rest, Config) ->
@ -182,6 +185,7 @@ config_test_() ->
unescaped_jsonp = true, unescaped_jsonp = true,
dirty_strings = true, dirty_strings = true,
strict_comments = true, strict_comments = true,
strict_commas = true,
strict_utf8 = true, strict_utf8 = true,
strict_single_quotes = true, strict_single_quotes = true,
strict_escapes = true, strict_escapes = true,
@ -199,6 +203,7 @@ config_test_() ->
{"strict flag", {"strict flag",
?_assertEqual( ?_assertEqual(
#config{strict_comments = true, #config{strict_comments = true,
strict_commas = true,
strict_utf8 = true, strict_utf8 = true,
strict_single_quotes = true, strict_single_quotes = true,
strict_escapes = true strict_escapes = true

View file

@ -4,6 +4,7 @@
unescaped_jsonp = false :: boolean(), unescaped_jsonp = false :: boolean(),
dirty_strings = false :: boolean(), dirty_strings = false :: boolean(),
strict_comments = false :: boolean(), strict_comments = false :: boolean(),
strict_commas = false :: boolean(),
strict_utf8 = false :: boolean(), strict_utf8 = false :: boolean(),
strict_single_quotes = false :: boolean(), strict_single_quotes = false :: boolean(),
strict_escapes = false :: boolean(), strict_escapes = false :: boolean(),

View file

@ -211,6 +211,8 @@ value(<<?start_array, Rest/binary>>, Handler, Stack, Config) ->
array(Rest, handle_event(start_array, Handler, Config), [array|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(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
value(Rest, Handler, Stack, Config); value(Rest, Handler, 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}) -> value(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
?error(value, <<?solidus, Rest/binary>>, Handler, Stack, Config); ?error(value, <<?solidus, Rest/binary>>, Handler, Stack, Config);
value(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) -> value(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
@ -289,6 +291,8 @@ key(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_
string(Rest, Handler, new_seq(), [singlequote|Stack], Config); string(Rest, Handler, new_seq(), [singlequote|Stack], Config);
key(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) -> key(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
key(Rest, 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(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) -> key(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
?error(key, <<?solidus, Rest/binary>>, Handler, Stack, Config); ?error(key, <<?solidus, Rest/binary>>, Handler, Stack, Config);
key(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) -> key(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
@ -1627,6 +1631,43 @@ bom_test_() ->
]. ].
trailing_comma_test_() ->
[
{"trailing comma in object", ?_assertEqual(
[start_object, {key, <<"key">>}, {literal, true}, end_object, end_json],
decode(<<"{\"key\": true,}">>, [])
)},
{"strict trailing comma in object", ?_assertError(
badarg,
decode(<<"{\"key\": true,}">>, [{strict, [trailing_commas]}])
)},
{"two trailing commas in object", ?_assertError(
badarg,
decode(<<"{\"key\": true,,}">>, [])
)},
{"comma in empty object", ?_assertError(
badarg,
decode(<<"{,}">>, [])
)},
{"trailing comma in list", ?_assertEqual(
[start_array, {literal, true}, end_array, end_json],
decode(<<"[true,]">>, [])
)},
{"strict trailing comma in list", ?_assertError(
badarg,
decode(<<"[true,]">>, [{strict, [trailing_commas]}])
)},
{"two trailing commas in list", ?_assertError(
badarg,
decode(<<"[true,,]">>, [])
)},
{"comma in empty list", ?_assertError(
badarg,
decode(<<"[,]">>, [])
)}
].
incomplete_test_() -> incomplete_test_() ->
[ [
{"stream false", ?_assertError( {"stream false", ?_assertError(