From 63faf04115260c7d88a3823429584b3cc88854e8 Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Fri, 2 Mar 2012 22:53:23 -0800 Subject: [PATCH] factor out gen_json --- README.markdown | 77 ++----------------------------------------- src/gen_json.erl | 80 --------------------------------------------- src/jsx.app.src | 1 - src/jsx.erl | 11 +++---- src/jsx_to_json.erl | 6 ++-- src/jsx_to_term.erl | 2 +- src/jsx_utils.erl | 18 +--------- src/jsx_verify.erl | 9 +++-- 8 files changed, 20 insertions(+), 184 deletions(-) delete mode 100644 src/gen_json.erl diff --git a/README.markdown b/README.markdown index 33ed083..2b3433d 100644 --- a/README.markdown +++ b/README.markdown @@ -6,12 +6,12 @@ copyright 2011, 2012 alisdair sullivan jsx is released under the terms of the [MIT][MIT] license - -## quickstart ## - to build jsx, `make` or `./rebar compile` +## api ## + + **converting json to erlang terms** parses a JSON text (a utf8 encoded binary) and produces an erlang term (see json <-> erlang mapping details below) @@ -173,77 +173,6 @@ json arrays are represented with erlang lists of json values as described in thi json objects are represented by erlang proplists. the empty object has the special representation `[{}]` to differentiate it from the empty list. ambiguities like `[true, false]` prevent using the shorthand form of property lists using atoms as properties. all properties must be tuples. all keys must be encoded as in `string`, above, or as atoms (which will be escaped and converted to binaries for presentation to handlers) -## gen_json ## - -jsx is implemented as a set of scanners that produce tokens consumed be functions that transform them into various representations. `gen_json` is an interface to allow arbitrary representations/actions to be taken upon scanning a json text or erlang term representation of a json text - - -**the gen_json parser** - -`gen_json:parser(Mod)` -> `Result` - -`gen_json:parser(Mod, Args)` -> `Result` - -`gen_json:parser(Mod, Args, Opts)` -> `Result` - -types: - -* `Mod` = module() -* `Args` = any() -* `Opts` = see note below - -`Mod` is the callback module implementing the `gen_json` behaviour - -`Args` will be passed to `Mod:init/1` as is - -`Result` will be the return from `Mod:handle_event(end_json, State)` - -in general, `Opts` will be passed as is to the scanner. the scanner will be automatically selected based on input type. to specify a specific scanner, you may use the options `{parser, Parser}` where `Parser` can currently be one of `auto`, `encoder` or `decoder`. `auto` is the default behaviour, `encoder` will only accept erlang terms (as in the mapping detailed above) and `decoder` will only accept json texts. note that to parse naked erlang terms as json, you MUST specify `{parser, encoder}`. more scanners may be added in the future - - -modules that implement the `gen_json` behaviour must implement the following two functions - - -**init** - -produces the initial state for a gen_json handler - -`init(Args)` -> `InitialState` - -types: - -* `Args` = `any()` -* `InitialState` = `any()` - -`Args` is the argument passed to `gen_json/2` above as `Args`. when `gen_json/1` is called, `Args` will equal `[]` - - -**handle_event** - -`handle_event/2` will be called for each token along with the current state of the handler and should produce a new state - -`handle_event(Event, State)` -> `NewState` - -types: - -* `Event` = - - `start_object` - - `end_object` - - `start_array` - - `end_array` - - `end_json` - - `{key, list()}` - - `{string, list()}` - - `{integer, integer()}` - - `{float, float()}` - - `{literal, true}` - - `{literal, false}` - - `{literal, null}` -* `State` = `any()` - -`Event` types are detailed in the mapping section, above. any cleanup in your handler should be done upon receiving `end_json` as it will always be the last token received - - ## acknowledgements ## paul davis, lloyd hilaiel, john engelhart, bob ippolito, fernando benavides and alex kropivny have all contributed to the development of jsx, whether they know it or not diff --git a/src/gen_json.erl b/src/gen_json.erl deleted file mode 100644 index d9818ee..0000000 --- a/src/gen_json.erl +++ /dev/null @@ -1,80 +0,0 @@ -%% The MIT License - -%% Copyright (c) 2011 Alisdair Sullivan - -%% Permission is hereby granted, free of charge, to any person obtaining a copy -%% of this software and associated documentation files (the "Software"), to deal -%% in the Software without restriction, including without limitation the rights -%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%% copies of the Software, and to permit persons to whom the Software is -%% furnished to do so, subject to the following conditions: - -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. - -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -%% THE SOFTWARE. - - --module(gen_json). - --export([behaviour_info/1]). --export([parser/1, parser/2, parser/3]). --export([handle_event/2, init/1]). - - --type events() :: [event()]. --type event() :: start_object - | end_object - | start_array - | end_array - | end_json - | {key, list()} - | {string, list()} - | {integer, integer()} - | {float, float()} - | {literal, true} - | {literal, false} - | {literal, null}. - --type opts() :: [opt()]. --type opt() :: loose_unicode - | escape_forward_slashes - | explicit_end - | {parser, auto} | {parser, encoder} | {parser, decoder} | {parser, function()}. - --export_type([events/0, event/0, opts/0, opt/0]). - - -behaviour_info(callbacks) -> [{init, 0}, {handle_event, 2}]; -behaviour_info(_) -> undefined. - - -parser(F) -> parser(F, []). - -parser(F, Opts) when is_function(F, 1) -> parser(?MODULE, {F, undefined}, Opts); -parser({F, State}, Opts) when is_function(F, 2) -> parser(?MODULE, {F, State}, Opts); -parser(Mod, Args) -> parser(Mod, Args, []). - -parser(Mod, Args, Opts) when is_atom(Mod), is_list(Opts) -> - case proplists:get_value(parser, Opts, auto) of - auto -> - fun(Input) when is_list(Input) -> (jsx_encoder:encoder(Mod, Mod:init(Args), Opts))(Input) - ; (Input) when is_binary(Input) -> (jsx_decoder:decoder(Mod, Mod:init(Args), Opts))(Input) - end - ; encoder -> - fun(Input) -> (jsx_encoder:encoder(Mod, Mod:init(Args), Opts))(Input) end - ; decoder -> - fun(Input) -> (jsx_decoder:decoder(Mod, Mod:init(Args), Opts))(Input) end - end. - - -handle_event(Event, {F, undefined}) -> F(Event), {F, undefined}; -handle_event(Event, {F, State}) -> {F, F(Event, State)}. - -init(State) -> State. \ No newline at end of file diff --git a/src/jsx.app.src b/src/jsx.app.src index 243ec9f..cfa0ad9 100644 --- a/src/jsx.app.src +++ b/src/jsx.app.src @@ -4,7 +4,6 @@ {vsn, "1.0rc"}, {modules, [ jsx, - gen_json, jsx_encoder, jsx_decoder, jsx_to_json, diff --git a/src/jsx.erl b/src/jsx.erl index 6d37ae9..2c5dc0a 100644 --- a/src/jsx.erl +++ b/src/jsx.erl @@ -42,7 +42,7 @@ to_json(Source) -> to_json(Source, []). -to_json(Source, Opts) -> jsx_to_json:to_json(Source, Opts ++ [{parser, encoder}]). +to_json(Source, Opts) -> jsx_to_json:to_json(Source, Opts). %% old api, alias for to_json/x @@ -55,8 +55,7 @@ term_to_json(Source, Opts) -> to_json(Source, Opts). format(Source) -> format(Source, []). -format(Source, Opts) -> - jsx_to_json:to_json(Source, Opts ++ [{parser, decoder}]). +format(Source, Opts) -> jsx_to_json:to_json(Source, Opts). -spec to_term(Source::binary()) -> any(). @@ -64,7 +63,7 @@ format(Source, Opts) -> to_term(Source) -> to_term(Source, []). -to_term(Source, Opts) -> jsx_to_term:to_term(Source, Opts ++ [{parser, decoder}]). +to_term(Source, Opts) -> jsx_to_term:to_term(Source, Opts). %% old api, alias for to_term/x @@ -158,7 +157,7 @@ parse_tests([], _Dir, Acc) -> decode(JSON, Flags) -> try - case (gen_json:parser(?MODULE, [], Flags))(JSON) of + case (jsx_decoder:decoder(?MODULE, [], Flags))(JSON) of {incomplete, More} -> case More(<<" ">>) of {incomplete, _} -> {error, badjson} @@ -172,7 +171,7 @@ decode(JSON, Flags) -> incremental_decode(<>, Flags) -> - P = gen_json:parser(?MODULE, [], Flags ++ [explicit_end]), + P = jsx_decoder:decoder(?MODULE, [], Flags ++ [explicit_end]), try incremental_decode_loop(P(C), Rest) catch error:badarg -> io:format("~p~n", [erlang:get_stacktrace()]), {error, badjson} end. diff --git a/src/jsx_to_json.erl b/src/jsx_to_json.erl index 07cf7e8..aa9089f 100644 --- a/src/jsx_to_json.erl +++ b/src/jsx_to_json.erl @@ -38,8 +38,10 @@ -spec to_json(Source::(binary() | list()), Opts::opts()) -> binary(). -to_json(Source, Opts) when is_list(Opts) -> - (gen_json:parser(?MODULE, Opts, jsx_utils:extract_opts(Opts)))(Source). +to_json(Source, Opts) when is_list(Source) andalso is_list(Opts) -> + (jsx_encoder:encoder(?MODULE, init(Opts), Opts))(Source); +to_json(Source, Opts) when is_binary(Source) andalso is_list(Opts) -> + (jsx_decoder:decoder(?MODULE, init(Opts), Opts))(Source). diff --git a/src/jsx_to_term.erl b/src/jsx_to_term.erl index 35b93ad..fac4b32 100644 --- a/src/jsx_to_term.erl +++ b/src/jsx_to_term.erl @@ -37,7 +37,7 @@ -spec to_term(Source::(binary() | list()), Opts::opts()) -> binary(). to_term(Source, Opts) when is_list(Opts) -> - (gen_json:parser(?MODULE, Opts, jsx_utils:extract_opts(Opts)))(Source). + (jsx_decoder:decoder(?MODULE, init(Opts), Opts))(Source). diff --git a/src/jsx_utils.erl b/src/jsx_utils.erl index ce9fb33..992736c 100644 --- a/src/jsx_utils.erl +++ b/src/jsx_utils.erl @@ -23,7 +23,7 @@ -module(jsx_utils). --export([parse_opts/1, extract_opts/1]). +-export([parse_opts/1]). -export([nice_decimal/1]). -export([json_escape/2]). @@ -49,22 +49,6 @@ parse_opts(_, _) -> {error, badarg}. -extract_opts(Opts) -> - extract_parser_opts(Opts, []). - -extract_parser_opts([], Acc) -> Acc; -extract_parser_opts([{K,V}|Rest], Acc) -> - case lists:member(K, [loose_unicode, escape_forward_slash, explicit_end, parser]) of - true -> extract_parser_opts(Rest, [{K,V}] ++ Acc) - ; false -> extract_parser_opts(Rest, Acc) - end; -extract_parser_opts([K|Rest], Acc) -> - case lists:member(K, [loose_unicode, escape_forward_slash, explicit_end]) of - true -> extract_parser_opts(Rest, [K] ++ Acc) - ; false -> extract_parser_opts(Rest, Acc) - end. - - %% conversion of floats to 'nice' decimal output. erlang's float implementation %% is almost but not quite ieee 754. it converts negative zero to plain zero diff --git a/src/jsx_verify.erl b/src/jsx_verify.erl index 36fdba9..ee21c74 100644 --- a/src/jsx_verify.erl +++ b/src/jsx_verify.erl @@ -36,9 +36,12 @@ -spec is_json(Source::(binary() | list()), Opts::opts()) -> binary(). -is_json(Source, Opts) when (is_binary(Source) andalso is_list(Opts)) - orelse (is_list(Source) andalso is_list(Opts)) -> - try (gen_json:parser(?MODULE, Opts, jsx_utils:extract_opts(Opts)))(Source) +is_json(Source, Opts) when is_list(Source) andalso is_list(Opts) -> + try (jsx_encoder:encoder(?MODULE, init(Opts), Opts))(Source) + catch error:badarg -> false + end; +is_json(Source, Opts) when is_binary(Source) andalso is_list(Opts) -> + try (jsx_decoder:decoder(?MODULE, init(Opts), Opts))(Source) catch error:badarg -> false end.