diff --git a/src/njson_decoder.erl b/src/njson_decoder.erl index 2f6bceb..66bd14f 100644 --- a/src/njson_decoder.erl +++ b/src/njson_decoder.erl @@ -310,10 +310,6 @@ empty_object(<>, Original, Skip, [?KEY, Map | Next]) -> OK :: {ok, Json}, Json :: njson:t(), Error :: njson:decode_error(). -number(<<$+, Bin/binary>>, Original, Skip, Next, Len) -> - number(Bin, Original, Skip, Next, Len + 1); -number(<<$-, Bin/binary>>, Original, Skip, Next, Len) -> - number(Bin, Original, Skip, Next, Len + 1); number(<<$., Bin/binary>>, Original, Skip, Next, Len) -> fraction(Bin, Original, Skip, Next, Len + 1); number(<>, Original, Skip, Next, Len) when D >= $0, D =< $9 -> @@ -482,22 +478,20 @@ unescape(<>, Original, Skip, Next, Len, Acc) -> Error :: njson:decode_error(). do_unescape(<>, Original, Skip, Next, Acc) -> case C of - $" -> - chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $"]); - $/ -> - chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $/]); - $\\ -> - chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $\\]); $b -> chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $\b]); - $f -> - chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $\f]); + $t -> + chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $\t]); $n -> chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $\n]); + $f -> + chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $\f]); $r -> chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $\r]); - $t -> - chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $\t]); + $" -> + chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $"]); + $\\ -> + chunk(Bin, Original, Skip + 1, Next, 0, [Acc, $\\]); $u -> unescape_unicode(Bin, Original, Skip + 1, Next, Acc) end. diff --git a/src/njson_encoder.erl b/src/njson_encoder.erl index cbeca4d..805fa20 100644 --- a/src/njson_encoder.erl +++ b/src/njson_encoder.erl @@ -16,6 +16,13 @@ %%% EXTERNAL EXPORTS -export([encode/2]). +%%% MACROS +-define(is_ascii_escape(C), + (C >= 16#00 andalso C =< 16#1F) orelse + (C =:= 16#22) orelse + (C =:= 16#5C) +). + %%%----------------------------------------------------------------------------- %%% EXTERNAL EXPORTS %%%----------------------------------------------------------------------------- @@ -189,33 +196,38 @@ escape(<<>>, Base, _Len, []) -> Base; escape(<<>>, Base, _Len, Acc) -> [Acc, Base]; -escape(<<$", Bin/binary>>, Base, Len, Acc) -> - String = binary:part(Base, 0, Len), - escape(Bin, Bin, 0, [Acc, String, $\\, $"]); -escape(<<$/, Bin/binary>>, Base, Len, Acc) -> - String = binary:part(Base, 0, Len), - escape(Bin, Bin, 0, [Acc, String, $\\, $/]); -escape(<<$\\, Bin/binary>>, Base, Len, Acc) -> - String = binary:part(Base, 0, Len), - escape(Bin, Bin, 0, [Acc, String, $\\, $\\]); -escape(<<$\b, Bin/binary>>, Base, Len, Acc) -> - String = binary:part(Base, 0, Len), - escape(Bin, Bin, 0, [Acc, String, $\\, $b]); -escape(<<$\f, Bin/binary>>, Base, Len, Acc) -> +escape(<>, Base, Len, Acc) when ?is_ascii_escape(C) -> String = binary:part(Base, 0, Len), - escape(Bin, Bin, 0, [Acc, String, $\\, $f]); -escape(<<$\n, Bin/binary>>, Base, Len, Acc) -> - String = binary:part(Base, 0, Len), - escape(Bin, Bin, 0, [Acc, String, $\\, $n]); -escape(<<$\r, Bin/binary>>, Base, Len, Acc) -> - String = binary:part(Base, 0, Len), - escape(Bin, Bin, 0, [Acc, String, $\\, $r]); -escape(<<$\t, Bin/binary>>, Base, Len, Acc) -> - String = binary:part(Base, 0, Len), - escape(Bin, Bin, 0, [Acc, String, $\\, $t]); + escape(Bin, Bin, 0, [Acc, String, escape_char(C)]); escape(<<_C, Bin/binary>>, Base, Len, Acc) -> escape(Bin, Base, Len + 1, Acc). +escape_char(C) when C >= 16#00 andalso C =< 16#07 -> + Bin = integer_to_binary(C, 16), + <<"\\u000", Bin/binary>>; +escape_char($\b) -> + <<"\\b">>; +escape_char($\t) -> + <<"\\t">>; +escape_char($\n) -> + <<"\\n">>; +escape_char($\x0b) -> + <<"\\u000B">>; +escape_char($\f) -> + <<"\\f">>; +escape_char($\r) -> + <<"\\r">>; +escape_char(C) when C =:= 16#0E orelse C =:= 16#0F -> + Bin = integer_to_binary(C, 16), + <<"\\u000", Bin/binary>>; +escape_char(C) when C >= 16#10 andalso C =< 16#1F -> + Bin = integer_to_binary(C, 16), + <<"\\u00", Bin/binary>>; +escape_char($") -> + <<"\\\"">>; +escape_char($\\) -> + <<"\\\\">>. + %%%----------------------------------------------------------------------------- %%% BIND FUNCTIONS %%%----------------------------------------------------------------------------- diff --git a/test/njson_SUITE.erl b/test/njson_SUITE.erl index 234ca24..9931f1c 100644 --- a/test/njson_SUITE.erl +++ b/test/njson_SUITE.erl @@ -119,7 +119,8 @@ json_encode(_Conf) -> end, lists:foreach(Test2, json_encode_only_cases() ++ json_cases()), {ok, _Json} = njson:encode(#{<<"key2">> => <<"Val2">>}, true), - {ok, _Json2} = njson:encode([<<"val1">>, <<"val2">>], true). + {ok, _Json2} = njson:encode([<<"val1">>, <<"val2">>], true), + {ok, _Json3} = njson:encode(<<"val1">>, true). json_decode_only_cases() -> [ @@ -128,7 +129,7 @@ json_decode_only_cases() -> {<<"+1.0E+3">>, 1.0e3}, {<<"+1E+3">>, 1.0e3}, {<<"false", $\s, $\t, $\r, $\n>>, false}, - {<<"\"\\\\, \\b, \\f, \\r, \\n\"">>, <<"\\, \b, \f, \r, \n">>}, + {<<"[true, false \t \n \r \s]">>, [true, false]}, {<<"\s\t\r\n { \r\n \"currency\" \t\r\n: \"\\u20AC\"}">>, #{ <<"currency">> => <<"€"/utf8>> }}, @@ -136,14 +137,15 @@ json_decode_only_cases() -> {<<"{\"key1\":\"Val1\", \"key2\":null, \"key3\":\"Val3\"}">>, #{ <<"key1">> => <<"Val1">>, <<"key2">> => null, <<"key3">> => <<"Val3">> }}, - - {<<"{\"d\":\"a6\\/\"}">>, #{<<"d">> => <<"a6/">>}}, + {<<"{\"key1\":\"Val1\"\t \n \r \s, \t \n \r \s}">>, #{ + <<"key1">> => <<"Val1">> + }}, + {<<"{\"d\":\"a6/\"}">>, #{<<"d">> => <<"a6/">>}}, {<<"null">>, null} ]. json_encode_only_cases() -> [ - {<<"\"\\\\, \\t, \\b, \\f, \\r, \\n\"">>, <<"\\, \t, \b, \f, \r, \n">>}, {<<"{\"listKey\":\"a6\"}">>, #{<<"listKey">> => <<"a6">>}}, {<<"{\"binaryKey\":\"a6\"}">>, #{<<"binaryKey">> => <<"a6">>}}, {<<"{}">>, #{}}, @@ -171,6 +173,10 @@ json_encode_only_cases() -> json_cases() -> [ + {<<"\"\\\\, \\\", \\t, \\b, \\f, \\r, \\n \"">>, <<"\\, \", \t, \b, \f, \r, \n ">>}, + {<<"\" \\u0000, \\t, \\u000B, \\r, \\u000E \"">>, <<" \x00, \t, \v, \x0D, \x0E ">>}, + {<<"\" \\u000F, \\u001D, \\u001F, ¹, ï \"">>, <<" \x0F, \x1D, \x1F, \xb9, \xef ">>}, + {<<"\" \\u0019, \\u000B, \\u000E, \\u000F \"">>, <<" \x19, \x0b, \x0e, \x0f ">>}, {<<"true">>, true}, {<<"false">>, false}, {<<"">>, undefined}, @@ -217,8 +223,6 @@ json_json_cases() -> {<<"false">>, false}, {<<"[]">>, []}, {<<"">>, undefined}, - {<<"\"ho\\\"l2\"">>, <<"ho\"l2">>}, - {<<"\"ho\\\"l\\\"2\"">>, <<"ho\"l\"2">>}, {<<"[true]">>, [true]}, {<<"[true,false]">>, [true, false]}, {<<"1">>, 1}, @@ -254,14 +258,16 @@ json_emoji() -> [{userdata, [{doc, "Properly decoding json with emojis"}]}]. json_emoji(_Conf) -> - HoFBin = <<"{\"text\":{\"body\":\"\\u2764\\u200d\\ud83d\\udd25\"}}">>, + HoFBin = <<"{\"text\":{\"body\":\"\\u2764\\u200d\\ud83d\\udd25\\u0bef\"}}">>, DecodedHoF = #{ - <<"text">> => #{<<"body">> => <<226, 157, 164, 226, 128, 141, 240, 159, 148, 165>>} + <<"text">> => #{ + <<"body">> => <<226, 157, 164, 226, 128, 141, 240, 159, 148, 165, 224, 175, 175>> + } }, ?assertEqual({ok, DecodedHoF}, njson:decode(HoFBin)), EncodedHoF = <<123, 34, 116, 101, 120, 116, 34, 58, 123, 34, 98, 111, 100, 121, 34, 58, 34, 226, 157, - 164, 226, 128, 141, 240, 159, 148, 165, 34, 125, 125>>, + 164, 226, 128, 141, 240, 159, 148, 165, 224, 175, 175, 34, 125, 125>>, ?assertEqual({ok, EncodedHoF}, njson:encode(DecodedHoF)), io:format("~tp~n", [DecodedHoF]), io:format("~ts~n", [EncodedHoF]). diff --git a/test/property_test/njson_properties.erl b/test/property_test/njson_properties.erl index 8d54b41..222050e 100644 --- a/test/property_test/njson_properties.erl +++ b/test/property_test/njson_properties.erl @@ -19,6 +19,13 @@ %%% EXPORTS -compile([export_all, nowarn_export_all]). +%%% MACROS +-define(is_ascii_escape(C), + (C >= 16#00 andalso C =< 16#1F) orelse + (C =:= 16#22) orelse + (C =:= 16#5C) +). + %%----------------------------------------------------------------------------- %%% PROPERTIES %%%----------------------------------------------------------------------------- @@ -243,7 +250,11 @@ json_char_escaped() -> oneof(json_char_escaped_list()). json_char_escaped_list() -> - [16#22, 16#5C, 16#2F, 16#8, 16#C, 16#A, 16#D, 16#9]. + [16#00, 16#01, 16#02, 16#03, 16#04, 16#05, 16#06, 16#07, + 16#08, 16#09, 16#0A, 16#0B, 16#0C, 16#0D, 16#0E, 16#0F, + 16#10, 16#11, 16#12, 16#13, 16#14, 16#15, 16#16, 16#17, + 16#18, 16#19, 16#1A, 16#1B, 16#1C, 16#1D, 16#1E, 16#1F, + 16#22, 16#5C]. json_number(Options) -> ?SUCHTHAT( @@ -332,21 +343,7 @@ pretty_print_json_array_elements([]) -> pretty_print_json_escape_chars(CharacterList) -> lists:map(fun escape_char/1, CharacterList). -escape_char(16#22) -> - [16#5C, 16#22]; -escape_char(16#5C) -> - [16#5C, 16#5C]; -escape_char(16#2F) -> - [16#5C, 16#2F]; -escape_char(16#8) -> - [16#5C, 16#62]; -escape_char(16#C) -> - [16#5C, 16#66]; -escape_char(16#A) -> - [16#5C, 16#6E]; -escape_char(16#D) -> - [16#5C, 16#72]; -escape_char(16#9) -> - [16#5C, 16#74]; +escape_char(C) when ?is_ascii_escape(C)-> + [16#5C, C]; escape_char(C) -> C.