From 3ea3eba7b3f914a6e6779a38bf25b1cda97dff8f Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Tue, 3 Aug 2010 20:29:49 -0700 Subject: [PATCH] first attempt at integrating json lib with jsx lib --- makefile | 2 +- src/jsx.erl | 8 +-- src/jsx_eep0018.erl | 14 +---- src/jsx_encoder.erl | 8 +-- src/jsx_format.erl | 140 +++++++++++++++++------------------------- src/jsx_verify.erl | 11 +--- test/jsx_test.escript | 37 ++++++++--- 7 files changed, 96 insertions(+), 124 deletions(-) diff --git a/makefile b/makefile index 0da1433..36cfa2a 100644 --- a/makefile +++ b/makefile @@ -16,4 +16,4 @@ clean: ./priv/backends.escript clean install: compile - ./rebar install \ No newline at end of file + ./rebar -f install \ No newline at end of file diff --git a/src/jsx.erl b/src/jsx.erl index 7e80643..b87d44a 100644 --- a/src/jsx.erl +++ b/src/jsx.erl @@ -71,7 +71,7 @@ term_to_json(JSON) -> term_to_json(JSON, []). term_to_json(JSON, Opts) -> - json_encoder:term_to_json(JSON, Opts). + jsx_encoder:term_to_json(JSON, Opts). -spec json_to_term(JSON::binary()) -> json(). @@ -81,7 +81,7 @@ json_to_term(JSON) -> json_to_term(JSON, []). json_to_term(JSON, Opts) -> - json_decoder:json_to_term(JSON, Opts). + jsx_eep0018:json_to_term(JSON, Opts). -spec is_json(JSON::binary()) -> true | false. @@ -91,7 +91,7 @@ is_json(JSON) -> is_json(JSON, []). is_json(JSON, Opts) -> - json_verify:is_json(JSON, Opts). + jsx_verify:is_json(JSON, Opts). -spec format(JSON::binary()) -> binary() | iolist(). @@ -101,7 +101,7 @@ format(JSON) -> format(JSON, []). format(JSON, Opts) -> - json_pp:pp(JSON, Opts). + jsx_format:format(JSON, Opts). %% ---------------------------------------------------------------------------- diff --git a/src/jsx_eep0018.erl b/src/jsx_eep0018.erl index 7e24281..6ea5ed3 100644 --- a/src/jsx_eep0018.erl +++ b/src/jsx_eep0018.erl @@ -80,17 +80,9 @@ collect({event, end_array, Next}, [Current, Parent|Rest], Opts) when is_list(Par collect({event, Start, Next}, [Current, Key, Parent|Rest], Opts) when Start =:= end_object; Start =:= end_array -> collect(Next(), [[{Key, lists:reverse(Current)}] ++ Parent] ++ Rest, Opts); - -%% end of json is emitted asap (at close of array/object), calling Next() until {incomplete, More} -%% and then More(end_stream) ensures the tail of the json binary is clean (whitespace only) -collect({event, end_json, Next}, [[Acc]], _Opts) -> - case Next() of - {incomplete, More} -> case More(end_stream) of - ok -> Acc - ; _ -> erlang:error(badarg) - end - ; _ -> erlang:error(badarg) - end; + +collect({event, end_json, _Next}, [[Acc]], _Opts) -> + Acc; %% key can only be emitted inside of a json object, so just insert it directly into %% the head of the accumulator and deal with it when we receive it's paired value diff --git a/src/jsx_encoder.erl b/src/jsx_encoder.erl index 2daf873..985a2b4 100644 --- a/src/jsx_encoder.erl +++ b/src/jsx_encoder.erl @@ -39,7 +39,7 @@ term_to_json(List, Opts) -> ; true -> erlang:error(badarg) end, Encoding = proplists:get_value(encoding, Opts, utf8), - json:format(event_generator(term_to_events(List)), [{output_encoding, Encoding}] ++ Opts). + jsx:format(event_generator(lists:reverse(term_to_events(List))), [{output_encoding, Encoding}] ++ Opts). event_generator([]) -> fun() -> {event, end_json, fun() -> {incomplete, fun(end_stream) -> ok end} end} end; @@ -48,7 +48,7 @@ event_generator([Next|Rest]) -> term_to_events([{}]) -> - [start_object, end_object]; + [end_object, start_object]; term_to_events([First|_] = List) when is_tuple(First) -> proplist_to_events(List, [start_object]); term_to_events(List) when is_list(List) -> @@ -65,7 +65,7 @@ proplist_to_events([{Key, Term}|Rest], Acc) -> ; true -> erlang:error(badarg) end; proplist_to_events([], Acc) -> - lists:reverse([end_object] ++ Acc); + [end_object] ++ Acc; proplist_to_events(_, _) -> erlang:throw(badarg). @@ -73,7 +73,7 @@ proplist_to_events(_, _) -> list_to_events([Term|Rest], Acc) -> list_to_events(Rest, term_to_event(Term) ++ Acc); list_to_events([], Acc) -> - lists:reverse([end_array] ++ Acc). + [end_array] ++ Acc. term_to_event(List) when is_list(List) -> diff --git a/src/jsx_format.erl b/src/jsx_format.erl index 09469bf..7dac05a 100644 --- a/src/jsx_format.erl +++ b/src/jsx_format.erl @@ -24,7 +24,7 @@ -module(jsx_format). -author("alisdairsullivan@yahoo.ca"). --export([pp/2]). +-export([format/2]). -include("./include/jsx_types.hrl"). @@ -53,12 +53,17 @@ -spec format(JSON::binary(), Opts::format_opts()) -> binary() | iolist(). -pp(F, Opts) when is_function(F) -> - prettify(F(), [], parse_opts(Opts, #opts{}), 0, start); - -pp(JSON, Opts) when is_binary(JSON) -> +format(JSON, Opts) when is_binary(JSON) -> P = jsx:parser(extract_parser_opts(Opts)), - prettify(P(JSON), [], parse_opts(Opts, #opts{}), 0, start). + format(fun() -> P(JSON) end, Opts); + +format(F, OptsList) when is_function(F) -> + Opts = parse_opts(OptsList, #opts{}), + {Continue, String} = format_something(F(), Opts, 0), + case Continue() of + {event, end_json, _} -> encode(String, Opts) + ; _ -> {error, badarg} + end. parse_opts([{indent, Val}|Rest], Opts) -> @@ -79,81 +84,58 @@ extract_parser_opts(Opts) -> [ {K, V} || {K, V} <- Opts, lists:member(K, [comments, encoding]) ]. -prettify({event, start_object, Next}, Acc, Opts, Level, start) -> - prettify(Next(), [Acc, ?start_object], Opts, Level + 1, new); -prettify({event, start_object, Next}, Acc, Opts, Level, _) -> - prettify(Next(), - [Acc, ?comma, space(Opts), indent(Opts, Level), ?start_object], - Opts, - Level + 1, - new); - -prettify({event, start_array, Next}, Acc, Opts, Level, start) -> - prettify(Next(), [Acc, ?start_array], Opts, Level + 1, new); -prettify({event, start_array, Next}, Acc, Opts, Level, _) -> - prettify(Next(), - [Acc, ?comma, space(Opts), indent(Opts, Level), ?start_array], - Opts, - Level + 1, - new); - -prettify({event, end_object, Next}, Acc, Opts, Level, value) -> - DeLevel = Level - 1, - prettify(Next(), [Acc, indent(Opts, DeLevel), ?end_object], Opts, DeLevel, value); -prettify({event, end_object, Next}, Acc, Opts, Level, new) -> - prettify(Next(), [Acc, ?end_object], Opts, Level - 1, value); +format_something({event, start_object, Next}, Opts, Level) -> + {Continue, Object} = format_object(Next(), [], Opts, Level + 1), + {Continue, [?start_object, Object, ?end_object]}; +format_something({event, start_array, Next}, Opts, Level) -> + {Continue, Array} = format_array(Next(), [], Opts, Level + 1), + {Continue, [?start_array, Array, ?end_array]}; +format_something({event, {Type, Value}, Next}, _Opts, _Level) -> + {Next, [encode(Type, Value)]}. -prettify({event, end_array, Next}, Acc, Opts, Level, value) -> - DeLevel = Level - 1, - prettify(Next(), [Acc, indent(Opts, DeLevel), ?end_array], Opts, DeLevel, value); -prettify({event, end_array, Next}, Acc, Opts, Level, new) -> - prettify(Next(), [Acc, ?end_array], Opts, Level - 1, value); + +format_object({event, end_object, Next}, Acc, _Opts, _Level) -> + {Next, Acc}; +format_object({event, {key, Key}, Next}, Acc, Opts, Level) -> + {Continue, Value} = format_something(Next(), Opts, Level), + case Continue() of + {event, end_object, NextNext} -> + {NextNext, [Acc, indent(Opts, Level), encode(string, Key), ?colon, space(Opts), Value]} + ; Else -> + format_object(Else, + [Acc, indent(Opts, Level), encode(string, Key), ?colon, space(Opts), Value, ?comma], + Opts, + Level + ) + end. + +format_array({event, end_array, Next}, Acc, _Opts, _Level) -> + {Next, Acc}; +format_array(Event, Acc, Opts, Level) -> + {Continue, Value} = format_something(Event, Opts, Level), + case Continue() of + {event, end_array, NextNext} -> + {NextNext, [Acc, indent(Opts, Level), Value]} + ; Else -> + format_array(Else, [Acc, indent(Opts, Level), Value, ?comma], Opts, Level) + end. -prettify({event, {key, Key}, Next}, Acc, Opts, Level, value) -> - prettify(Next(), - [Acc, ?comma, space(Opts), indent(Opts, Level), format(string, Key), ?colon, space(Opts)], - Opts, - Level, - key); -prettify({event, {key, Key}, Next}, Acc, Opts, Level, _) -> - prettify(Next(), - [Acc, indent(Opts, Level), format(string, Key), ?colon, space(Opts)], - Opts, - Level, - key); -prettify({event, {Type, Value}, Next}, Acc, Opts, Level, value) -> - prettify(Next(), - [Acc, ?comma, space(Opts), indent(Opts, Level), format(Type, Value)], - Opts, - Level, - value); -prettify({event, {Type, Value}, Next}, Acc, Opts, Level, new) -> - prettify(Next(), [Acc, indent(Opts, Level), format(Type, Value)], Opts, Level, value); -prettify({event, {Type, Value}, Next}, Acc, Opts, Level, key) -> - prettify(Next(), [Acc, format(Type, Value)], Opts, Level, value); -prettify({event, {Type, Value}, Next}, _Acc, Opts, Level, start) -> - case Opts#opts.strict of - true -> erlang:throw(badarg) - ; false -> prettify(Next(), [format(Type, Value)], Opts, Level, error) - end; +-define(is_utf_encoding(X), + X == utf8; X == utf16; X == utf32; X == {utf16, little}; X == {utf32, little} +). -prettify({event, end_json, Next}, Acc, Opts, _, _) -> - case Next() of - {incomplete, More} -> case More(end_stream) of - ok -> encode(Acc, Opts) - ; _ -> erlang:throw(badarg) - end +encode(Acc, Opts) when is_list(Acc) -> + case Opts#opts.output_encoding of + iolist -> Acc + ; UTF when ?is_utf_encoding(UTF) -> unicode:characters_to_binary(Acc, utf8, UTF) ; _ -> erlang:throw(badarg) end; - -prettify(_, _, _, _, error) -> erlang:throw(badarg). - -format(string, String) -> +encode(string, String) -> [?quote, String, ?quote]; -format(literal, Literal) -> +encode(literal, Literal) -> erlang:atom_to_list(Literal); -format(_, Number) -> +encode(_, Number) -> Number. @@ -175,16 +157,4 @@ space(Opts) -> case Opts#opts.space of 0 -> [] ; X when X > 0 -> [ ?space || _ <- lists:seq(1, X) ] - end. - - --define(is_utf_encoding(X), - X == utf8; X == utf16; X == utf32; X == {utf16, little}; X == {utf32, little} -). - -encode(Acc, Opts) -> - case Opts#opts.output_encoding of - iolist -> Acc - ; UTF when ?is_utf_encoding(UTF) -> unicode:characters_to_binary(Acc, utf8, UTF) - ; _ -> erlang:throw(badarg) end. \ No newline at end of file diff --git a/src/jsx_verify.erl b/src/jsx_verify.erl index 49198c1..70e4921 100644 --- a/src/jsx_verify.erl +++ b/src/jsx_verify.erl @@ -49,16 +49,9 @@ collect_strict({event, start_array, Next}, Keys) -> collect_strict(_, _) -> false. -%% make sure to ensure tail is clean -collect({event, end_json, Next}, _Keys) -> - case Next() of - {incomplete, More} -> case More(end_stream) of - ok -> true - ; _ -> false - end - ; _ -> false - end; +collect({event, end_json, _Next}, _Keys) -> + true; %% check to see if key has already been encountered, if not add it to the key accumulator %% and continue, else return false diff --git a/test/jsx_test.escript b/test/jsx_test.escript index 9f88319..a8d0137 100755 --- a/test/jsx_test.escript +++ b/test/jsx_test.escript @@ -26,6 +26,14 @@ -export([main/1]). +-define(to_json(X, Y, N), + etap:is(jsx:term_to_json(X), Y, N) +). + +-define(to_erep(X, Y, N), + etap:is(jsx:json_to_term(X), Y, N) +). + main([]) -> test("./test/cases"); @@ -38,9 +46,20 @@ test(Dir) -> ValidJSONTests = load_tests(Dir), - etap:plan((length(ValidJSONTests) * 10) + 1), - run_tests(ValidJSONTests), + etap:plan((length(ValidJSONTests) * 10) + 9), + run_jsx_tests(ValidJSONTests), + etap:is(multi_decode(multi_json_body(), []), multi_test_result(), "multi terms"), + + ?to_erep(<<"{}">>, [{}], "empty object to erep"), + ?to_json([{}], <<"{}">>, "empty object to json"), + ?to_erep(<<"[]">>, [], "empty array to erep"), + ?to_json([], <<"[]">>, "empty array to json"), + ?to_erep(<<"{ \"key\": \"value\", \"another key\": [] }">>, [{<<"key">>, <<"value">>}, {<<"another key">>, []}], "object to erep"), + ?to_json([{<<"key">>, <<"value">>}, {<<"another key">>, []}], <<"{\"key\":\"value\",\"another key\":[]}">>, "object to json"), + ?to_erep(<<"[true, 1, -0.5e7, \"hello world\"]">>, [true, 1, -0.5e7, <<"hello world">>], "array to erep"), + ?to_json([true, 1, -0.5e7, <<"hello world">>], <<"[true,1,-5000000.0,\"hello world\"]">>, "array to json"), + etap:end_tests(). @@ -62,9 +81,9 @@ load_tests([Test|Rest], Dir, Acc) -> end catch _:_ -> load_tests(Rest, Dir, Acc) end. -run_tests([]) -> +run_jsx_tests([]) -> ok; -run_tests([{TestName, JSON, Events, Flags}|Rest]) -> +run_jsx_tests([{TestName, JSON, Events, Flags}|Rest]) -> etap:is(decode(JSON, Flags), Events, TestName ++ ": utf8"), etap:is(incremental_decode(JSON, Flags), Events, TestName ++ ": incremental utf8"), etap:is(decode(to_utf16(JSON), Flags), Events, TestName ++ ": utf16"), @@ -75,14 +94,14 @@ run_tests([{TestName, JSON, Events, Flags}|Rest]) -> etap:is(incremental_decode(to_utf32(JSON), Flags), Events, TestName ++ ": incremental utf32"), etap:is(decode(to_utf32le(JSON), Flags), Events, TestName ++ ": utf32le"), etap:is(incremental_decode(to_utf32le(JSON), Flags), Events, TestName ++ ": incremental utf32le"), - run_tests(Rest). + run_jsx_tests(Rest). decode(JSON, Flags) -> P = jsx:parser(Flags), decode_loop(P(JSON), []). -decode_loop({event, end_json, Next}, Acc) -> +decode_loop({event, end_json, _Next}, Acc) -> lists:reverse([end_json] ++ Acc); decode_loop({incomplete, More}, Acc) -> decode_loop(More(end_stream), Acc); @@ -98,7 +117,7 @@ incremental_decode_loop({incomplete, Next}, <<>>, Acc) -> incremental_decode_loop(Next(end_stream), <<>>, Acc); incremental_decode_loop({incomplete, Next}, <>, Acc) -> incremental_decode_loop(Next(C), Rest, Acc); -incremental_decode_loop({event, end_json, Next}, _Rest, Acc) -> +incremental_decode_loop({event, end_json, _Next}, _Rest, Acc) -> lists:reverse([end_json] ++ Acc); incremental_decode_loop({event, Event, Next}, Rest, Acc) -> incremental_decode_loop(Next(), Rest, [Event] ++ Acc). @@ -139,6 +158,4 @@ multi_test_result() -> [start_array, end_array], [start_array, {integer, "1"}, {integer, "2"}, {integer, "3"}, end_array], [{string, "hope this works"}] - ]. - - \ No newline at end of file + ]. \ No newline at end of file