factor out gen_json
This commit is contained in:
parent
61ef73ff72
commit
63faf04115
8 changed files with 20 additions and 184 deletions
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -4,7 +4,6 @@
|
|||
{vsn, "1.0rc"},
|
||||
{modules, [
|
||||
jsx,
|
||||
gen_json,
|
||||
jsx_encoder,
|
||||
jsx_decoder,
|
||||
jsx_to_json,
|
||||
|
|
11
src/jsx.erl
11
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(<<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)
|
||||
catch error:badarg -> io:format("~p~n", [erlang:get_stacktrace()]), {error, badjson}
|
||||
end.
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue