Merge branch 'develop' into vtwopointoh

This commit is contained in:
alisdair sullivan 2013-12-17 02:03:21 +00:00
commit 3bb65e9bab
12 changed files with 104 additions and 45 deletions

View file

@ -1,3 +1,14 @@
v1.4.5
* various fixes to typespecs uncovered by dialyzer
* allow integer keys during encoding
* convert atoms (other than `true`, `false` and `null`) to strings during encoding
v1.4.4
* typespec for `json_term/0` fixed
* incorrect boolean shortcircuiting fixed in multibyte escape processing
v1.4.3 v1.4.3
* add empty rebar.config for mix build tool * add empty rebar.config for mix build tool

View file

@ -144,8 +144,8 @@ ignores bad escape sequences
**json** | **erlang** **json** | **erlang**
--------------------------------|-------------------------------- --------------------------------|--------------------------------
`number` | `integer()` if possible, `float()` otherwise `number` | `integer()` and `float()`
`string` | `binary()` `string` | `binary()` and `atom()`
`true`, `false` and `null` | `true`, `false` and `null` `true`, `false` and `null` | `true`, `false` and `null`
`array` | `[]` and `[JSON]` `array` | `[]` and `[JSON]`
`object` | `[{}]` and `[{binary() OR atom(), JSON}]` `object` | `[{}]` and `[{binary() OR atom(), JSON}]`
@ -171,9 +171,10 @@ ignores bad escape sequences
* strings * strings
json strings must be unicode. in practice, because **jsx** only accepts json strings must be unicode encoded binaries or erlang atoms. in practice,
`utf8` all strings must be `utf8`. in addition to being unicode json strings because **jsx** only accepts `utf8` binaries all binary strings must be `utf8`.
restrict a number of codepoints and define a number of escape sequences in addition to being unicode json strings restrict a number of codepoints and
define a number of escape sequences
json string escapes of the form `\uXXXX` will be converted to their json string escapes of the form `\uXXXX` will be converted to their
equivalent codepoints during parsing. this means control characters and equivalent codepoints during parsing. this means control characters and
@ -208,9 +209,9 @@ ignores bad escape sequences
special representation `[{}]` to differentiate it from the empty list. special representation `[{}]` to differentiate it from the empty list.
ambiguities like `[true, false]` prevent the use of the shorthand form of ambiguities like `[true, false]` prevent the use of the shorthand form of
property lists using atoms as properties so all properties must be tuples. property lists using atoms as properties so all properties must be tuples.
all keys must be encoded as in `string` or as atoms (which will be escaped all keys must be encoded as in `string` or as atoms or integers (which will
and converted to binaries for presentation to handlers). values should be be escaped and converted to binaries for presentation to handlers). values
valid json values should be valid json values
### incomplete input ### ### incomplete input ###
@ -254,6 +255,7 @@ json_term() = [json_term()]
| integer() | integer()
| float() | float()
| binary() | binary()
| atom()
``` ```
the erlang representation of json. binaries should be `utf8` encoded, or close the erlang representation of json. binaries should be `utf8` encoded, or close
@ -631,7 +633,7 @@ following events must be handled:
## acknowledgements ## ## acknowledgements ##
**jsx** wouldn't be what it is without the contributions of [paul davis](https://github.com/davisp), [lloyd hilaiel](https://github.com/lloyd), [john engelhart](https://github.com/johnezang), [bob ippolito](https://github.com/etrepum), [fernando benavides](https://github.com/elbrujohalcon), [alex kropivny](https://github.com/amtal), [steve strong](https://github.com/srstrong), [michael truog](https://github.com/okeuday), [dmitry kolesnikov](https://github.com/fogfish) and [emptytea](https://github.com/emptytea) jsx wouldn't be what it is without the contributions of [paul davis](https://github.com/davisp), [lloyd hilaiel](https://github.com/lloyd), [john engelhart](https://github.com/johnezang), [bob ippolito](https://github.com/etrepum), [fernando benavides](https://github.com/elbrujohalcon), [alex kropivny](https://github.com/amtal), [steve strong](https://github.com/srstrong), [michael truog](https://github.com/okeuday), [devin torres](https://github.com/devinus), [dmitry kolesnikov](https://github.com/fogfish), [emptytea](https://github.com/emptytea), [john daily](https://github.com/macintux), [ola bäckström](https://github.com/olabackstrom), [joseph crowe](https://github.com/JosephCrowe), [patrick gombert](https://github.com/patrickgombert), [eskuat](https://github.com/eskuat) and [max lapshin](https://github.com/maxlapshin)
[json]: http://json.org [json]: http://json.org
[yajl]: http://lloyd.github.com/yajl [yajl]: http://lloyd.github.com/yajl

View file

@ -1,7 +1,7 @@
{application, jsx, {application, jsx,
[ [
{description, "a streaming, evented json parsing toolkit"}, {description, "a streaming, evented json parsing toolkit"},
{vsn, "1.4.3"}, {vsn, "1.4.4"},
{modules, [ {modules, [
jsx, jsx,
jsx_encoder, jsx_encoder,

View file

@ -48,11 +48,11 @@
| null | null
| integer() | integer()
| float() | float()
| binary(). | binary()
| atom().
-type json_text() :: binary(). -type json_text() :: binary().
-spec encode(Source::json_term()) -> json_text() | {incomplete, encoder()}. -spec encode(Source::json_term()) -> json_text() | {incomplete, encoder()}.
-spec encode(Source::json_term(), Config::jsx_to_json:config()) -> json_text() | {incomplete, encoder()}. -spec encode(Source::json_term(), Config::jsx_to_json:config()) -> json_text() | {incomplete, encoder()}.

View file

@ -33,8 +33,22 @@
-include("jsx_config.hrl"). -include("jsx_config.hrl").
-type handler_type(Handler) ::
fun((jsx:json_text() | end_stream |
jsx:json_term(),
{decoder, any(), module(), null | list(), list()} |
{parser, any(), module(), list()} |
{encoder, any(), module()},
list({pre_encode, fun((any()) -> any())} |
{error_handler, Handler} |
{incomplete_handler, Handler} |
atom())) -> any()).
-type handler() :: handler_type(handler()).
-export_type([handler/0]).
%% parsing of jsx config %% parsing of jsx config
-spec parse_config(Config::proplists:proplist()) -> jsx:config().
parse_config(Config) -> parse_config(Config, #config{}). parse_config(Config) -> parse_config(Config, #config{}).
parse_config([], Config) -> Config; parse_config([], Config) -> Config;
@ -83,6 +97,8 @@ parse_strict(_Strict, _Rest, _Config) ->
-spec config_to_list(Config::jsx:config()) -> proplists:proplist().
config_to_list(Config) -> config_to_list(Config) ->
reduce_config(lists:map( reduce_config(lists:map(
fun ({error_handler, F}) -> {error_handler, F}; fun ({error_handler, F}) -> {error_handler, F};
@ -116,6 +132,8 @@ reduce_config([Else|Input], Output, Strict) ->
reduce_config(Input, [Else] ++ Output, Strict). reduce_config(Input, [Else] ++ Output, Strict).
-spec valid_flags() -> [atom()].
valid_flags() -> valid_flags() ->
[ [
escaped_forward_slashes, escaped_forward_slashes,
@ -129,6 +147,8 @@ valid_flags() ->
]. ].
-spec extract_config(Config::proplists:proplist()) -> proplists:proplist().
extract_config(Config) -> extract_config(Config) ->
extract_parser_config(Config, []). extract_parser_config(Config, []).

View file

@ -1,13 +1,13 @@
-record(config, { -record(config, {
escaped_forward_slashes = false, escaped_forward_slashes = false :: boolean(),
escaped_strings = false, escaped_strings = false :: boolean(),
unescaped_jsonp = false, unescaped_jsonp = false :: boolean(),
dirty_strings = false, dirty_strings = false :: boolean(),
strict_comments = false, strict_comments = false :: boolean(),
strict_utf8 = false, strict_utf8 = false :: boolean(),
strict_single_quotes = false, strict_single_quotes = false :: boolean(),
strict_escapes = false, strict_escapes = false :: boolean(),
stream = false, stream = false :: boolean(),
error_handler = false, error_handler = false :: false | jsx_config:handler(),
incomplete_handler = false incomplete_handler = false :: false | jsx_config:handler()
}). }).

View file

@ -33,7 +33,7 @@
-export([decoder/3, resume/6]). -export([decoder/3, resume/6]).
-spec decoder(Handler::module(), State::any(), Config::jsx:config()) -> jsx:decoder(). -spec decoder(Handler::module(), State::any(), Config::list()) -> jsx:decoder().
decoder(Handler, State, Config) -> decoder(Handler, State, Config) ->
fun(JSON) -> start(JSON, {Handler, Handler:init(State)}, [], jsx_config:parse_config(Config)) end. fun(JSON) -> start(JSON, {Handler, Handler:init(State)}, [], jsx_config:parse_config(Config)) end.
@ -48,7 +48,7 @@ decoder(Handler, State, Config) ->
Acc::any(), Acc::any(),
Stack::list(atom()), Stack::list(atom()),
Config::jsx:config() Config::jsx:config()
) -> jsx:decoder(). ) -> jsx:decoder() | {incomplete, jsx:decoder()}.
resume(Rest, State, Handler, Acc, Stack, Config) -> resume(Rest, State, Handler, Acc, Stack, Config) ->
case State of case State of
@ -671,12 +671,12 @@ unescape(<<$u, $d, A, B, C, ?rsolidus, $u, W, X, Y, Z, Rest/binary>>, Handler, A
false -> string(Rest, Handler, acc_seq(Acc, [16#fffd, 16#fffd]), Stack, Config) false -> string(Rest, Handler, acc_seq(Acc, [16#fffd, 16#fffd]), Stack, Config)
end; end;
unescape(<<$u, $d, A, B, C, ?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config) unescape(<<$u, $d, A, B, C, ?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config)
when (A == $8 orelse A == $9 orelse A == $a orelse A == $b) andalso when (A == $8 orelse A == $9 orelse A == $a orelse A == $b),
?is_hex(B), ?is_hex(C) ?is_hex(B), ?is_hex(C)
-> ->
incomplete(string, <<?rsolidus, $u, $d, A, B, C, ?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config); incomplete(string, <<?rsolidus, $u, $d, A, B, C, ?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config);
unescape(<<$u, $d, A, B, C>>, Handler, Acc, Stack, Config) unescape(<<$u, $d, A, B, C>>, Handler, Acc, Stack, Config)
when (A == $8 orelse A == $9 orelse A == $a orelse A == $b) andalso when (A == $8 orelse A == $9 orelse A == $a orelse A == $b),
?is_hex(B), ?is_hex(C) ?is_hex(B), ?is_hex(C)
-> ->
incomplete(string, <<?rsolidus, $u, $d, A, B, C>>, Handler, Acc, Stack, Config); incomplete(string, <<?rsolidus, $u, $d, A, B, C>>, Handler, Acc, Stack, Config);

View file

@ -25,7 +25,7 @@
-export([encoder/3, encode/1, encode/2, unzip/1]). -export([encoder/3, encode/1, encode/2, unzip/1]).
-spec encoder(Handler::module(), State::any(), Config::jsx:config()) -> jsx:encoder(). -spec encoder(Handler::module(), State::any(), Config::list()) -> jsx:encoder().
encoder(Handler, State, Config) -> encoder(Handler, State, Config) ->
Parser = jsx:parser(Handler, State, Config), Parser = jsx:parser(Handler, State, Config),
@ -57,8 +57,7 @@ encode(Else, _EntryPoint) -> [Else].
unzip(List) -> unzip(List, []). unzip(List) -> unzip(List, []).
unzip([], Acc) -> lists:reverse(Acc); unzip([], Acc) -> lists:reverse(Acc);
unzip([{K, V}|Rest], Acc) when is_binary(K); is_atom(K) -> unzip(Rest, [V, K] ++ Acc). unzip([{K, V}|Rest], Acc) when is_binary(K); is_atom(K); is_integer(K) -> unzip(Rest, [V, K] ++ Acc).
-ifdef(TEST). -ifdef(TEST).
@ -67,6 +66,7 @@ unzip([{K, V}|Rest], Acc) when is_binary(K); is_atom(K) -> unzip(Rest, [V, K] ++
parser(Term, Opts) -> (jsx:parser(jsx, [], Opts))(Term). parser(Term, Opts) -> (jsx:parser(jsx, [], Opts))(Term).
error_test_() -> error_test_() ->
[ [
{"value error", ?_assertError(badarg, parser(self(), []))}, {"value error", ?_assertError(badarg, parser(self(), []))},

View file

@ -27,7 +27,7 @@
-export([init/1, handle_event/2]). -export([init/1, handle_event/2]).
-spec parser(Handler::module(), State::any(), Config::jsx:config()) -> jsx:parser(). -spec parser(Handler::module(), State::any(), Config::list()) -> jsx:parser().
parser(Handler, State, Config) -> parser(Handler, State, Config) ->
fun(Tokens) -> value(Tokens, {Handler, Handler:init(State)}, [], jsx_config:parse_config(Config)) end. fun(Tokens) -> value(Tokens, {Handler, Handler:init(State)}, [], jsx_config:parse_config(Config)) end.
@ -36,12 +36,12 @@ parser(Handler, State, Config) ->
%% resume allows continuation from interrupted decoding without having to explicitly export %% resume allows continuation from interrupted decoding without having to explicitly export
%% all states %% all states
-spec resume( -spec resume(
Rest::binary(), Rest::jsx:token(),
State::atom(), State::atom(),
Handler::{atom(), any()}, Handler::{atom(), any()},
Stack::list(atom()), Stack::list(atom()),
Config::jsx:config() Config::jsx:config()
) -> jsx:parser(). ) -> jsx:parser() | {incomplete, jsx:parser()}.
resume(Rest, State, Handler, Stack, Config) -> resume(Rest, State, Handler, Stack, Config) ->
case State of case State of
@ -84,8 +84,6 @@ incomplete(State, Handler, Stack, Config=#config{incomplete_handler=F}) ->
F([], {parser, State, Handler, Stack}, jsx_config:config_to_list(Config)). F([], {parser, State, Handler, Stack}, jsx_config:config_to_list(Config)).
handle_event([], Handler, _Config) -> Handler;
handle_event([Event|Rest], Handler, Config) -> handle_event(Rest, handle_event(Event, Handler, Config), Config);
handle_event(Event, {Handler, State}, _Config) -> {Handler, Handler:handle_event(Event, State)}. handle_event(Event, {Handler, State}, _Config) -> {Handler, Handler:handle_event(Event, State)}.
@ -128,13 +126,13 @@ value(Token, Handler, Stack, Config) ->
object([end_object|Tokens], Handler, [object|Stack], Config) -> object([end_object|Tokens], Handler, [object|Stack], Config) ->
maybe_done(Tokens, handle_event(end_object, Handler, Config), Stack, Config); maybe_done(Tokens, handle_event(end_object, Handler, Config), Stack, Config);
object([{key, Key}|Tokens], Handler, Stack, Config) when is_atom(Key); is_binary(Key) -> object([{key, Key}|Tokens], Handler, Stack, Config) when is_atom(Key); is_binary(Key); is_integer(Key) ->
case clean_string(fix_key(Key), Tokens, Handler, Stack, Config) of case clean_string(fix_key(Key), Tokens, Handler, Stack, Config) of
Clean when is_binary(Clean) -> Clean when is_binary(Clean) ->
value(Tokens, handle_event({key, Clean}, Handler, Config), Stack, Config); value(Tokens, handle_event({key, Clean}, Handler, Config), Stack, Config);
Error -> Error Error -> Error
end; end;
object([Key|Tokens], Handler, Stack, Config) when is_atom(Key); is_binary(Key) -> object([Key|Tokens], Handler, Stack, Config) when is_atom(Key); is_binary(Key); is_integer(Key) ->
case clean_string(fix_key(Key), Tokens, Handler, Stack, Config) of case clean_string(fix_key(Key), Tokens, Handler, Stack, Config) of
Clean when is_binary(Clean) -> Clean when is_binary(Clean) ->
value(Tokens, handle_event({key, Clean}, Handler, Config), Stack, Config); value(Tokens, handle_event({key, Clean}, Handler, Config), Stack, Config);
@ -178,7 +176,8 @@ done(Token, Handler, Stack, Config) ->
done([Token], Handler, Stack, Config). done([Token], Handler, Stack, Config).
fix_key(Key) when is_atom(Key) -> fix_key(atom_to_binary(Key, utf8)); fix_key(Key) when is_atom(Key) -> atom_to_binary(Key, utf8);
fix_key(Key) when is_integer(Key) -> list_to_binary(integer_to_list(Key));
fix_key(Key) when is_binary(Key) -> Key. fix_key(Key) when is_binary(Key) -> Key.
@ -440,8 +439,12 @@ to_hex(X) -> X + 48. %% ascii "1" is [49], "2" is [50], etc...
%% for raw input %% for raw input
-spec init(proplists:proplist()) -> list().
init([]) -> []. init([]) -> [].
-spec handle_event(Event::any(), Acc::list()) -> list().
handle_event(end_json, State) -> lists:reverse(State); handle_event(end_json, State) -> lists:reverse(State);
handle_event(Event, State) -> [Event] ++ State. handle_event(Event, State) -> [Event] ++ State.
@ -997,4 +1000,11 @@ json_escape_sequence_test_() ->
]. ].
fix_key_test_() ->
[
{"binary key", ?_assertEqual(fix_key(<<"foo">>), <<"foo">>)},
{"atom key", ?_assertEqual(fix_key(foo), <<"foo">>)},
{"integer key", ?_assertEqual(fix_key(123), <<"123">>)}
].
-endif. -endif.

View file

@ -35,6 +35,7 @@
}). }).
-type config() :: list(). -type config() :: list().
-export_type([config/0]).
-spec to_json(Source::any(), Config::config()) -> binary(). -spec to_json(Source::any(), Config::config()) -> binary().
@ -85,9 +86,14 @@ parse_config([], Config) ->
-define(newline, <<"\n">>). -define(newline, <<"\n">>).
-type state() :: {unicode:charlist(), #config{}}.
-spec init(Config::proplists:proplist()) -> state().
init(Config) -> {[], parse_config(Config)}. init(Config) -> {[], parse_config(Config)}.
-spec handle_event(Event::any(), State::state()) -> state().
handle_event(end_json, {Term, _Config}) -> Term; handle_event(end_json, {Term, _Config}) -> Term;
handle_event(start_object, State) -> start_object(State); handle_event(start_object, State) -> start_object(State);

View file

@ -33,6 +33,8 @@
}). }).
-type config() :: list(). -type config() :: list().
-export_type([config/0]).
-type json_value() :: list({binary(), json_value()}) -type json_value() :: list({binary(), json_value()})
| list(json_value()) | list(json_value())
@ -70,9 +72,12 @@ parse_config([K|Rest] = Options, Config) ->
parse_config([], Config) -> parse_config([], Config) ->
Config. Config.
-type state() :: {[any()], #config{}}.
-spec init(Config::proplists:proplist()) -> state().
init(Config) -> {[], parse_config(Config)}. init(Config) -> {[], parse_config(Config)}.
-spec handle_event(Event::any(), State::state()) -> state().
handle_event(end_json, {Term, _Config}) -> Term; handle_event(end_json, {Term, _Config}) -> Term;

View file

@ -32,6 +32,7 @@
}). }).
-type config() :: []. -type config() :: [].
-export_type([config/0]).
-spec is_json(Source::binary(), Config::config()) -> true | false. -spec is_json(Source::binary(), Config::config()) -> true | false.
@ -72,10 +73,14 @@ parse_config([K|Rest] = Options, Config) ->
parse_config([], Config) -> parse_config([], Config) ->
Config. Config.
-type state() :: {#config{}, any()}.
-spec init(Config::proplists:proplist()) -> state().
init(Config) -> {parse_config(Config), []}. init(Config) -> {parse_config(Config), []}.
-spec handle_event(Event::any(), State::state()) -> state().
handle_event(end_json, _) -> true; handle_event(end_json, _) -> true;
handle_event(_, {Config, _} = State) when Config#config.repeated_keys == true -> State; handle_event(_, {Config, _} = State) when Config#config.repeated_keys == true -> State;