factor out gen_json

This commit is contained in:
alisdair sullivan 2012-03-02 22:53:23 -08:00
parent 61ef73ff72
commit 63faf04115
8 changed files with 20 additions and 184 deletions

View file

@ -6,12 +6,12 @@ copyright 2011, 2012 alisdair sullivan
jsx is released under the terms of the [MIT][MIT] license jsx is released under the terms of the [MIT][MIT] license
## quickstart ##
to build jsx, `make` or `./rebar compile` to build jsx, `make` or `./rebar compile`
## api ##
**converting json to erlang terms** **converting json to erlang terms**
parses a JSON text (a utf8 encoded binary) and produces an erlang term (see json <-> erlang mapping details below) 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) 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 ## ## 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 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

View file

@ -1,80 +0,0 @@
%% The MIT License
%% Copyright (c) 2011 Alisdair Sullivan <alisdairsullivan@yahoo.ca>
%% 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.

View file

@ -4,7 +4,6 @@
{vsn, "1.0rc"}, {vsn, "1.0rc"},
{modules, [ {modules, [
jsx, jsx,
gen_json,
jsx_encoder, jsx_encoder,
jsx_decoder, jsx_decoder,
jsx_to_json, jsx_to_json,

View file

@ -42,7 +42,7 @@
to_json(Source) -> to_json(Source, []). 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 %% 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) -> format(Source, []).
format(Source, Opts) -> format(Source, Opts) -> jsx_to_json:to_json(Source, Opts).
jsx_to_json:to_json(Source, Opts ++ [{parser, decoder}]).
-spec to_term(Source::binary()) -> any(). -spec to_term(Source::binary()) -> any().
@ -64,7 +63,7 @@ format(Source, Opts) ->
to_term(Source) -> to_term(Source, []). 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 %% old api, alias for to_term/x
@ -158,7 +157,7 @@ parse_tests([], _Dir, Acc) ->
decode(JSON, Flags) -> decode(JSON, Flags) ->
try try
case (gen_json:parser(?MODULE, [], Flags))(JSON) of case (jsx_decoder:decoder(?MODULE, [], Flags))(JSON) of
{incomplete, More} -> {incomplete, More} ->
case More(<<" ">>) of case More(<<" ">>) of
{incomplete, _} -> {error, badjson} {incomplete, _} -> {error, badjson}
@ -172,7 +171,7 @@ decode(JSON, Flags) ->
incremental_decode(<<C:1/binary, Rest/binary>>, Flags) -> incremental_decode(<<C:1/binary, Rest/binary>>, Flags) ->
P = gen_json:parser(?MODULE, [], Flags ++ [explicit_end]), P = jsx_decoder:decoder(?MODULE, [], Flags ++ [explicit_end]),
try incremental_decode_loop(P(C), Rest) try incremental_decode_loop(P(C), Rest)
catch error:badarg -> io:format("~p~n", [erlang:get_stacktrace()]), {error, badjson} catch error:badarg -> io:format("~p~n", [erlang:get_stacktrace()]), {error, badjson}
end. end.

View file

@ -38,8 +38,10 @@
-spec to_json(Source::(binary() | list()), Opts::opts()) -> binary(). -spec to_json(Source::(binary() | list()), Opts::opts()) -> binary().
to_json(Source, Opts) when is_list(Opts) -> to_json(Source, Opts) when is_list(Source) andalso is_list(Opts) ->
(gen_json:parser(?MODULE, Opts, jsx_utils:extract_opts(Opts)))(Source). (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).

View file

@ -37,7 +37,7 @@
-spec to_term(Source::(binary() | list()), Opts::opts()) -> binary(). -spec to_term(Source::(binary() | list()), Opts::opts()) -> binary().
to_term(Source, Opts) when is_list(Opts) -> 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).

View file

@ -23,7 +23,7 @@
-module(jsx_utils). -module(jsx_utils).
-export([parse_opts/1, extract_opts/1]). -export([parse_opts/1]).
-export([nice_decimal/1]). -export([nice_decimal/1]).
-export([json_escape/2]). -export([json_escape/2]).
@ -49,22 +49,6 @@ parse_opts(_, _) ->
{error, badarg}. {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 %% 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 %% is almost but not quite ieee 754. it converts negative zero to plain zero

View file

@ -36,9 +36,12 @@
-spec is_json(Source::(binary() | list()), Opts::opts()) -> binary(). -spec is_json(Source::(binary() | list()), Opts::opts()) -> binary().
is_json(Source, Opts) when (is_binary(Source) andalso is_list(Opts)) is_json(Source, Opts) when is_list(Source) andalso is_list(Opts) ->
orelse (is_list(Source) andalso is_list(Opts)) -> try (jsx_encoder:encoder(?MODULE, init(Opts), Opts))(Source)
try (gen_json:parser(?MODULE, Opts, jsx_utils:extract_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 catch error:badarg -> false
end. end.