fix formatting of empty lists/objects when converted to json

This commit is contained in:
alisdair sullivan 2014-04-27 22:47:09 +00:00
parent 4fba4f6c1c
commit 698a733ec1

View file

@ -26,7 +26,7 @@
-export([to_json/2, format/2]). -export([to_json/2, format/2]).
-export([init/1, handle_event/2]). -export([init/1, handle_event/2]).
-export([start_json/0, start_json/1]). -export([start_json/0, start_json/1]).
-export([start_object/1, start_array/1, finish/1, insert/2, insert/3, get_key/1, get_value/1]). -export([start_object/1, start_array/1, finish/1, insert/2, get_key/1, get_value/1]).
-record(config, { -record(config, {
@ -156,21 +156,31 @@ start_json() -> {[], #config{}}.
start_json(Config) when is_list(Config) -> {[], parse_config(Config)}. start_json(Config) when is_list(Config) -> {[], parse_config(Config)}.
%% allocate a new object on top of the stack %% allocate a new object on top of the stack
start_object({Stack, Config}) -> {[{object, ?start_object}] ++ Stack, Config}. start_object({Stack, Config = #config{depth = Depth}}) ->
{[{object, ?start_object}] ++ Stack, Config#config{depth = Depth + 1}}.
%% allocate a new array on top of the stack %% allocate a new array on top of the stack
start_array({Stack, Config}) -> {[{array, ?start_array}] ++ Stack, Config}. start_array({Stack, Config = #config{depth = Depth}}) ->
{[{array, ?start_array}] ++ Stack, Config#config{depth = Depth + 1}}.
%% finish an object or array and insert it into the parent object if it exists %% finish an object or array and insert it into the parent object if it exists
finish({[{object, Object}], Config}) -> finish({Stack, Config = #config{depth = Depth}}) ->
{<<Object/binary, ?end_object/binary>>, Config}; NewConfig = Config#config{depth = Depth - 1},
finish({[{object, Object}|Rest], Config}) -> finish_({Stack, NewConfig}).
insert(<<Object/binary, ?end_object/binary>>, {Rest, Config});
finish({[{array, Array}], Config}) -> finish_({[{object, <<"{">>}], Config}) -> {<<"{}">>, Config};
{<<Array/binary, ?end_array/binary>>, Config}; finish_({[{array, <<"[">>}], Config}) -> {<<"[]">>, Config};
finish({[{array, Array}|Rest], Config}) -> finish_({[{object, <<"{">>}|Rest], Config}) -> insert(<<"{}">>, {Rest, Config});
insert(<<Array/binary, ?end_array/binary>>, {Rest, Config}); finish_({[{array, <<"[">>}|Rest], Config}) -> insert(<<"[]">>, {Rest, Config});
finish(_) -> erlang:error(badarg). finish_({[{object, Object}], Config}) ->
{<<Object/binary, (indent(Config))/binary, ?end_object/binary>>, Config};
finish_({[{object, Object}|Rest], Config}) ->
insert(<<Object/binary, (indent(Config))/binary, ?end_object/binary>>, {Rest, Config});
finish_({[{array, Array}], Config}) ->
{<<Array/binary, (indent(Config))/binary, ?end_array/binary>>, Config};
finish_({[{array, Array}|Rest], Config}) ->
insert(<<Array/binary, (indent(Config))/binary, ?end_array/binary>>, {Rest, Config});
finish_(_) -> erlang:error(badarg).
%% insert a value when there's no parent object or array %% insert a value when there's no parent object or array
insert(Value, {[], Config}) when is_binary(Value) -> insert(Value, {[], Config}) when is_binary(Value) ->
@ -181,6 +191,7 @@ insert(Key, {[{object, Object}|Rest], Config}) when is_binary(Key) ->
insert(Value, {[{object, Key, ?start_object}|Rest], Config}) when is_binary(Value) -> insert(Value, {[{object, Key, ?start_object}|Rest], Config}) when is_binary(Value) ->
{ {
[{object, <<?start_object/binary, [{object, <<?start_object/binary,
(indent(Config))/binary,
Key/binary, Key/binary,
?colon/binary, ?colon/binary,
(space(Config))/binary, (space(Config))/binary,
@ -201,7 +212,7 @@ insert(Value, {[{object, Key, Object}|Rest], Config}) when is_binary(Value) ->
Config Config
}; };
insert(Value, {[{array, ?start_array}|Rest], Config}) when is_binary(Value) -> insert(Value, {[{array, ?start_array}|Rest], Config}) when is_binary(Value) ->
{[{array, <<?start_array/binary, Value/binary>>}] ++ Rest, Config}; {[{array, <<?start_array/binary, (indent(Config))/binary, Value/binary>>}] ++ Rest, Config};
insert(Value, {[{array, Array}|Rest], Config}) when is_binary(Value) -> insert(Value, {[{array, Array}|Rest], Config}) when is_binary(Value) ->
{ {
[{array, <<Array/binary, [{array, <<Array/binary,
@ -213,31 +224,6 @@ insert(Value, {[{array, Array}|Rest], Config}) when is_binary(Value) ->
}; };
insert(_, _) -> erlang:error(badarg). insert(_, _) -> erlang:error(badarg).
%% insert a key/value pair into an object
insert(Key, Value, {[{object, ?start_object}|Rest], Config}) when is_binary(Key), is_binary(Value) ->
{
[{object, <<?start_object/binary,
Key/binary,
?colon/binary,
(space(Config))/binary,
Value/binary
>>}] ++ Rest,
Config
};
insert(Key, Value, {[{object, Object}|Rest], Config}) when is_binary(Key), is_binary(Value) ->
{
[{object, <<Object/binary,
?comma/binary,
(indent_or_space(Config))/binary,
Key/binary,
?colon/binary,
(space(Config))/binary,
Value/binary
>>}] ++ Rest,
Config
};
insert(_, _, _) -> erlang:error(badarg).
get_key({[{object, Key, _}|_], _}) -> Key; get_key({[{object, Key, _}|_], _}) -> Key;
get_key(_) -> erlang:error(badarg). get_key(_) -> erlang:error(badarg).
@ -325,7 +311,7 @@ indent_or_space_test_() ->
]. ].
format_test_() -> encode_test_() ->
[ [
{"0.0", ?_assert(encode(float, 0.0, #config{}) =:= <<"0.0">>)}, {"0.0", ?_assert(encode(float, 0.0, #config{}) =:= <<"0.0">>)},
{"1.0", ?_assert(encode(float, 1.0, #config{}) =:= <<"1.0">>)}, {"1.0", ?_assert(encode(float, 1.0, #config{}) =:= <<"1.0">>)},
@ -382,19 +368,19 @@ rep_manipulation_test_() ->
start_json([{space, 1}, {indent, 2}]) start_json([{space, 1}, {indent, 2}])
)}, )},
{"allocate a new object on an empty stack", ?_assertEqual( {"allocate a new object on an empty stack", ?_assertEqual(
{[{object, <<"{">>}], #config{}}, {[{object, <<"{">>}], #config{depth=1}},
start_object({[], #config{}}) start_object({[], #config{}})
)}, )},
{"allocate a new object on a stack", ?_assertEqual( {"allocate a new object on a stack", ?_assertEqual(
{[{object, <<"{">>}, {object, <<"{">>}], #config{}}, {[{object, <<"{">>}, {object, <<"{">>}], #config{depth=1}},
start_object({[{object, <<"{">>}], #config{}}) start_object({[{object, <<"{">>}], #config{}})
)}, )},
{"allocate a new array on an empty stack", ?_assertEqual( {"allocate a new array on an empty stack", ?_assertEqual(
{[{array, <<"[">>}], #config{}}, {[{array, <<"[">>}], #config{depth=1}},
start_array({[], #config{}}) start_array({[], #config{}})
)}, )},
{"allocate a new array on a stack", ?_assertEqual( {"allocate a new array on a stack", ?_assertEqual(
{[{array, <<"[">>}, {object, <<"{">>}], #config{}}, {[{array, <<"[">>}, {object, <<"{">>}], #config{depth=1}},
start_array({[{object, <<"{">>}], #config{}}) start_array({[{object, <<"{">>}], #config{}})
)}, )},
{"insert a key into an object", ?_assertEqual( {"insert a key into an object", ?_assertEqual(
@ -421,36 +407,55 @@ rep_manipulation_test_() ->
{[{array, <<"[true">>}], #config{}}, {[{array, <<"[true">>}], #config{}},
insert(<<"true">>, {[{array, <<"[">>}], #config{}}) insert(<<"true">>, {[{array, <<"[">>}], #config{}})
)}, )},
{"insert a key/value pair into an object", ?_assertEqual(
{[{object, <<"{\"x\":true,\"y\":false">>}], #config{}},
insert(<<"\"y\"">>, <<"false">>, {[{object, <<"{\"x\":true">>}], #config{}})
)},
{"finish an object with no ancestor", ?_assertEqual( {"finish an object with no ancestor", ?_assertEqual(
{<<"{\"x\":true,\"y\":false}">>, #config{}}, {<<"{\"x\":true,\"y\":false}">>, #config{}},
finish({[{object, <<"{\"x\":true,\"y\":false">>}], #config{}}) finish({[{object, <<"{\"x\":true,\"y\":false">>}], #config{depth=1}})
)}, )},
{"finish an empty object", ?_assertEqual( {"finish an empty object", ?_assertEqual(
{<<"{}">>, #config{}}, {<<"{}">>, #config{}},
finish({[{object, <<"{">>}], #config{}}) finish({[{object, <<"{">>}], #config{depth=1}})
)}, )},
{"finish an object with an ancestor", ?_assertEqual( {"finish an object with an ancestor", ?_assertEqual(
{[{object, <<"{\"a\":[],\"b\":{\"x\":true,\"y\":false}">>}], #config{}}, {[{object, <<"{\"a\":[],\"b\":{\"x\":true,\"y\":false}">>}], #config{}},
finish({ finish({
[{object, <<"{\"x\":true,\"y\":false">>}, {object, <<"\"b\"">>, <<"{\"a\":[]">>}], [{object, <<"{\"x\":true,\"y\":false">>}, {object, <<"\"b\"">>, <<"{\"a\":[]">>}],
#config{} #config{depth=1}
}) })
)}, )},
{"finish an array with no ancestor", ?_assertEqual( {"finish an array with no ancestor", ?_assertEqual(
{<<"[true,false,null]">>, #config{}}, {<<"[true,false,null]">>, #config{}},
finish({[{array, <<"[true,false,null">>}], #config{}}) finish({[{array, <<"[true,false,null">>}], #config{depth=1}})
)}, )},
{"finish an array with an ancestor", ?_assertEqual( {"finish an array with an ancestor", ?_assertEqual(
{[{array, <<"[1,2,3,[true,false,null]">>}], #config{}}, {[{array, <<"[1,2,3,[true,false,null]">>}], #config{}},
finish({[{array, <<"[true,false,null">>}, {array, <<"[1,2,3">>}], #config{}}) finish({[{array, <<"[true,false,null">>}, {array, <<"[1,2,3">>}], #config{depth=1}})
)} )}
]. ].
format_test_() ->
% {minified version, pretty version}
Cases = [
{"empty object", <<"{}">>, <<"{}">>},
{"empty array", <<"[]">>, <<"[]">>},
{"single key object", <<"{\"k\":\"v\"}">>, <<"{\n \"k\": \"v\"\n}">>},
{"single member array", <<"[true]">>, <<"[\n true\n]">>},
{"multiple key object",
<<"{\"k\":\"v\",\"x\":\"y\"}">>,
<<"{\n \"k\": \"v\",\n \"x\": \"y\"\n}">>
},
{"multiple member array",
<<"[1.0,2.0,3.0]">>,
<<"[\n 1.0,\n 2.0,\n 3.0\n]">>
},
{"nested structure",
<<"[[{},[],true],{\"k\":\"v\",\"x\":\"y\"}]">>,
<<"[\n [\n {},\n [],\n true\n ],\n {\n \"k\": \"v\",\n \"x\": \"y\"\n }\n]">>
}
],
[{Title, ?_assertEqual(Min, jsx:minify(Pretty))} || {Title, Min, Pretty} <- Cases] ++
[{Title, ?_assertEqual(Pretty, jsx:prettify(Min))} || {Title, Min, Pretty} <- Cases].
handle_event_test_() -> handle_event_test_() ->
Data = jsx:test_cases() ++ jsx:special_test_cases(), Data = jsx:test_cases() ++ jsx:special_test_cases(),
[ [