Browse files

fixes for json2.erl and json.erl (Nico Kruber)

Implement more precise floating point number conversions -- avoid
converting via io_lib:format/2 by using erlang:float_to_list/1. Fix
encoding lists of (small) floats (json.erl mistakenly tried to convert
them to strings). Fix/add encoding of lists to {array, List}. Handle
unicode in object keys.
  • Loading branch information...
1 parent 4451864 commit 661aaf9cdab88bc6ff372dc47932d3b0a7d503d2 @vinoski vinoski committed Jun 1, 2011
Showing with 49 additions and 33 deletions.
  1. +23 −17 src/json.erl
  2. +26 −16 src/json2.erl
View
40 src/json.erl
@@ -79,7 +79,6 @@
%%% syntactically valid JSON floating-point numbers could silently
%%% lose precision or noisily cause an overflow. However, most
%%% other JSON libraries are likely to behave in the same way.
-%%% The encoding precision defaults to 6 digits.
%%%
%%% Strings: If we represented JSON string data as Erlang binaries,
%%% we would have to choose a particular unicode format. Instead,
@@ -123,12 +122,12 @@ encode(null) -> "null";
encode(undefined) -> "null";
encode(B) when is_binary(B) -> encode_string(B);
encode(I) when is_integer(I) -> integer_to_list(I);
-encode(F) when is_float(F) -> io_lib:format("~g", [F]);
+encode(F) when is_float(F) -> float_to_list(F);
encode(L) when is_list(L) ->
case is_string(L) of
yes -> encode_string(L);
unicode -> encode_string(xmerl_ucs:to_utf8(L));
- no -> exit({json_encode, {not_string, L}})
+ no -> encode({array, L})
end;
encode({array, Props}) when is_list(Props) -> encode_array(Props);
encode({struct, Props} = T) when is_list(Props) -> encode_object(T);
@@ -167,11 +166,16 @@ encode_string([C | Cs], Acc) ->
encode_object({struct, _Props} = Obj) ->
M = obj_fold(fun({Key, Value}, Acc) ->
S = case Key of
- B when is_binary(B) -> encode_string(B);
- L when is_list(L) -> encode_string(L);
- A when is_atom(A) -> encode_string(atom_to_list(A));
- _ -> exit({json_encode, {bad_key, Key}})
- end,
+ B when is_binary(B) -> encode_string(B);
+ L when is_list(L) ->
+ case is_string(L) of
+ yes -> encode_string(L);
+ unicode -> encode_string(xmerl_ucs:to_utf8(L));
+ no -> exit({json_encode, {bad_key, Key}})
+ end;
+ A when is_atom(A) -> encode_string(atom_to_list(A));
+ _ -> exit({json_encode, {bad_key, Key}})
+ end,
V = encode(Value),
case Acc of
[] -> [S, $:, V];
@@ -309,23 +313,23 @@ scan_number([D | Ds], A, X) when A > 0, D >= $0, D =< $9 ->
% Note that nonzero numbers can't start with "0".
scan_number(Ds, 10 * A + (D - $0), X);
scan_number([D | Ds], A, X) when D == $E; D == $e ->
- scan_exponent_begin(Ds, float(A), X);
+ scan_exponent_begin(Ds, integer_to_list(A) ++ ".0", X);
scan_number([D | _] = Ds, A, _X) when D < $0; D > $9 ->
{done, {ok, A}, Ds}.
scan_fraction(Ds, I, X) -> scan_fraction(Ds, [], I, X).
scan_fraction([], _Fs, _I, X) -> {more, X};
scan_fraction(eof, Fs, I, _X) ->
- R = I + list_to_float("0." ++ lists:reverse(Fs)),
+ R = list_to_float(lists:append([integer_to_list(I), ".", lists:reverse(Fs)])),
{done, {ok, R}, eof};
scan_fraction([D | Ds], Fs, I, X) when D >= $0, D =< $9 ->
scan_fraction(Ds, [D | Fs], I, X);
scan_fraction([D | Ds], Fs, I, X) when D == $E; D == $e ->
- R = I + list_to_float("0." ++ lists:reverse(Fs)),
+ R = lists:append([integer_to_list(I), ".", lists:reverse(Fs)]),
scan_exponent_begin(Ds, R, X);
scan_fraction(Rest, Fs, I, _X) ->
- R = I + list_to_float("0." ++ lists:reverse(Fs)),
+ R = list_to_float(lists:append([integer_to_list(I), ".", lists:reverse(Fs)])),
{done, {ok, R}, Rest}.
scan_exponent_begin(Ds, R, X) ->
@@ -340,12 +344,12 @@ scan_exponent_begin([D | Ds], Es, R, X) when D == $-;
scan_exponent([], _Es, _R, X) -> {more, X};
scan_exponent(eof, Es, R, _X) ->
- X = R * math:pow(10, list_to_integer(lists:reverse(Es))),
+ X = list_to_float(lists:append([R, "e", lists:reverse(Es)])),
{done, {ok, X}, eof};
scan_exponent([D | Ds], Es, R, X) when D >= $0, D =< $9 ->
scan_exponent(Ds, [D | Es], R, X);
scan_exponent(Rest, Es, R, _X) ->
- X = R * math:pow(10, list_to_integer(lists:reverse(Es))),
+ X = list_to_float(lists:append([R, "e", lists:reverse(Es)])),
{done, {ok, X}, Rest}.
scan_comment([]) -> {more, "/"};
@@ -573,7 +577,9 @@ obj_is_key(Key, {struct, Props}) ->
%% Store a new member in an object. Returns a new object.
obj_store(KeyList, Value, {struct, Props}) when is_list(Props) ->
- Key = list_to_atom(KeyList),
+ Key = try list_to_atom(KeyList)
+ catch error:badarg -> KeyList
+ end,
{struct, [{Key, Value} | proplists:delete(Key, Props)]}.
%% Create an object from a list of Key/Value pairs.
@@ -594,8 +600,8 @@ obj_fold(Fun, Acc, {struct, Props}) ->
is_string([]) -> yes;
is_string(List) -> is_string(List, non_unicode).
-is_string([C|Rest], non_unicode) when C >= 0, C =< 255 -> is_string(Rest, non_unicode);
-is_string([C|Rest], _) when C =< 65000 -> is_string(Rest, unicode);
+is_string([C|Rest], non_unicode) when is_integer(C), C >= 0, C =< 255 -> is_string(Rest, non_unicode);
+is_string([C|Rest], _) when is_integer(C), C>= 0, C =< 65000 -> is_string(Rest, unicode);
is_string([], non_unicode) -> yes;
is_string([], unicode) -> unicode;
is_string(_, _) -> no.
View
42 src/json2.erl
@@ -50,7 +50,6 @@
%%% syntactically valid JSON floating-point numbers could silently
%%% lose precision or noisily cause an overflow. However, most
%%% other JSON libraries are likely to behave in the same way.
-%%% The encoding precision defaults to 6 digits.
%%%
%%% Strings: If we represented JSON string data as Erlang binaries,
%%% we would have to choose a particular unicode format. Instead,
@@ -82,12 +81,12 @@ encode(null) -> "null";
encode(undefined) -> "null";
encode(B) when is_binary(B) -> encode_string(B);
encode(I) when is_integer(I) -> integer_to_list(I);
-encode(F) when is_float(F) -> io_lib:format("~g", [F]);
+encode(F) when is_float(F) -> float_to_list(F);
encode(L) when is_list(L) ->
case is_string(L) of
yes -> encode_string(L);
unicode -> encode_string(xmerl_ucs:to_utf8(L));
- no -> exit({json_encode, {not_string, L}})
+ no -> encode({array, L})
end;
encode({array, Props}) when is_list(Props) -> encode_array(Props);
encode({struct, Props} = T) when is_list(Props) -> encode_object(T);
@@ -126,11 +125,16 @@ encode_string([C | Cs], Acc) ->
encode_object({struct, _Props} = Obj) ->
M = obj_fold(fun({Key, Value}, Acc) ->
S = case Key of
- B when is_binary(B) -> encode_string(B);
- L when is_list(L) -> encode_string(L);
- A when is_atom(A) -> encode_string(atom_to_list(A));
- _ -> exit({json_encode, {bad_key, Key}})
- end,
+ B when is_binary(B) -> encode_string(B);
+ L when is_list(L) ->
+ case is_string(L) of
+ yes -> encode_string(L);
+ unicode -> encode_string(xmerl_ucs:to_utf8(L));
+ no -> exit({json_encode, {bad_key, Key}})
+ end;
+ A when is_atom(A) -> encode_string(atom_to_list(A));
+ _ -> exit({json_encode, {bad_key, Key}})
+ end,
V = encode(Value),
case Acc of
[] -> [S, $:, V];
@@ -264,23 +268,23 @@ scan_number([D | Ds], A, X) when A > 0, D >= $0, D =< $9 ->
% Note that nonzero numbers can't start with "0".
scan_number(Ds, 10 * A + (D - $0), X);
scan_number([D | Ds], A, X) when D == $E; D == $e ->
- scan_exponent_begin(Ds, float(A), X);
+ scan_exponent_begin(Ds, integer_to_list(A) ++ ".0", X);
scan_number([D | _] = Ds, A, _X) when D < $0; D > $9 ->
{done, {ok, A}, Ds}.
scan_fraction(Ds, I, X) -> scan_fraction(Ds, [], I, X).
scan_fraction([], _Fs, _I, X) -> {more, X};
scan_fraction(eof, Fs, I, _X) ->
- R = I + list_to_float("0." ++ lists:reverse(Fs)),
+ R = list_to_float(lists:append([integer_to_list(I), ".", lists:reverse(Fs)])),
{done, {ok, R}, eof};
scan_fraction([D | Ds], Fs, I, X) when D >= $0, D =< $9 ->
scan_fraction(Ds, [D | Fs], I, X);
scan_fraction([D | Ds], Fs, I, X) when D == $E; D == $e ->
- R = I + list_to_float("0." ++ lists:reverse(Fs)),
+ R = lists:append([integer_to_list(I), ".", lists:reverse(Fs)]),
scan_exponent_begin(Ds, R, X);
scan_fraction(Rest, Fs, I, _X) ->
- R = I + list_to_float("0." ++ lists:reverse(Fs)),
+ R = list_to_float(lists:append([integer_to_list(I), ".", lists:reverse(Fs)])),
{done, {ok, R}, Rest}.
scan_exponent_begin(Ds, R, X) ->
@@ -295,12 +299,12 @@ scan_exponent_begin([D | Ds], Es, R, X) when D == $-;
scan_exponent([], _Es, _R, X) -> {more, X};
scan_exponent(eof, Es, R, _X) ->
- X = R * math:pow(10, list_to_integer(lists:reverse(Es))),
+ X = list_to_float(lists:append([R, "e", lists:reverse(Es)])),
{done, {ok, X}, eof};
scan_exponent([D | Ds], Es, R, X) when D >= $0, D =< $9 ->
scan_exponent(Ds, [D | Es], R, X);
scan_exponent(Rest, Es, R, _X) ->
- X = R * math:pow(10, list_to_integer(lists:reverse(Es))),
+ X = list_to_float(lists:append([R, "e", lists:reverse(Es)])),
{done, {ok, X}, Rest}.
%%% PARSING
@@ -527,9 +531,9 @@ obj_fold(Fun, Acc, {struct, Props}) ->
is_string([]) -> yes;
is_string(List) -> is_string(List, non_unicode).
-is_string([C|Rest], non_unicode) when C >= 0, C =< 255 ->
+is_string([C|Rest], non_unicode) when is_integer(C), C >= 0, C =< 255 ->
is_string(Rest, non_unicode);
-is_string([C|Rest], _) when C =< 65000 -> is_string(Rest, unicode);
+is_string([C|Rest], _) when is_integer(C), C =< 65000 -> is_string(Rest, unicode);
is_string([], non_unicode) -> yes;
is_string([], unicode) -> unicode;
is_string(_, _) -> no.
@@ -581,6 +585,12 @@ test() ->
%% Legitimate changes to the encoding routines may require tweaks to
%% the reference JSON strings in e2j_test_vec().
+test_e2j(E, _) when is_float(E) ->
+ J2 = lists:flatten(encode(E)),
+ E2 = list_to_float(J2),
+ Rel = abs(E2 - E)/E,
+ true = Rel < 0.005,
+ ok;
test_e2j(E, J) ->
J2 = lists:flatten(encode(E)),
J = J2, % raises error if unequal

0 comments on commit 661aaf9

Please sign in to comment.