Browse Source

Support map

Takeru Ohta 10 years ago
parent
commit
3277c40d0d
8 changed files with 62 additions and 15 deletions
  1. 3 1
      README.md
  2. 15 2
      doc/jsone.md
  3. 2 5
      rebar.config
  4. 7 2
      src/jsone.erl
  5. 6 5
      src/jsone_decode.erl
  6. 1 0
      src/jsone_encode.erl
  7. 16 0
      test/jsone_decode_tests.erl
  8. 12 0
      test/jsone_encode_tests.erl

+ 3 - 1
README.md

@@ -1,4 +1,4 @@
-jsone (0.3.0)
+jsone (0.3.1)
 =============
 
 An Erlang library for encoding, decoding [JSON](http://json.org/index.html) data.
@@ -117,6 +117,8 @@ abc                  -> "abc"         -> <<"abc">> % non-special atom is regarde
 {[{key, <<"val">>}]} -> {"key":"val"} -> {[{<<"key">>, <<"val">>}]} % object_format=tuple
 [{}]                 -> {}            -> [{}]                       % object_format=proplist
 [{<<"key">>, val}]   -> {"key":"val"} -> [{<<"key">>, <<"val">>}]   % object_format=proplist
+#{}                  -> {}            -> #{}                        % object_format=map
+#{key => val}        -> {"key":"val"} -> #{<<"key">> => <<"val">>}  % object_format=map
 ```
 
 API

+ 15 - 2
doc/jsone.md

@@ -22,7 +22,7 @@ JSON decoding/encoding module.
 
 
 <pre><code>
-decode_option() = {object_format, tuple | proplist}
+decode_option() = {object_format, tuple | proplist | map}
 </code></pre>
 
 
@@ -31,6 +31,7 @@ decode_option() = {object_format, tuple | proplist}
 - Decoded JSON object format <br />
 - `tuple`: An object is decoded as `{[]}` if it is empty, otherwise `{[{Key, Value}]}`. <br />
 - `proplist`: An object is decoded as `[{}]` if it is empty, otherwise `[{Key, Value}]`. <br />
+- `map`: An object is decoded as `#{}` if it is empty, otherwise `#{Key => Value}`. <br />
 - default: `tuple` <br />
 
 
@@ -90,7 +91,19 @@ json_number() = number()
 
 
 <pre><code>
-json_object() = <a href="#type-json_object_format_tuple">json_object_format_tuple()</a> | <a href="#type-json_object_format_proplist">json_object_format_proplist()</a>
+json_object() = <a href="#type-json_object_format_tuple">json_object_format_tuple()</a> | <a href="#type-json_object_format_proplist">json_object_format_proplist()</a> | <a href="#type-json_object_format_map">json_object_format_map()</a>
+</code></pre>
+
+
+
+
+
+### <a name="type-json_object_format_map">json_object_format_map()</a> ###
+
+
+
+<pre><code>
+json_object_format_map() = #{}
 </code></pre>
 
 

+ 2 - 5
rebar.config

@@ -1,4 +1,6 @@
 %% -*- erlang -*-
+{require_min_otp_vsn, "OTP17"}.
+
 {erl_opts, [
             warnings_as_errors,
             warn_export_all,
@@ -24,8 +26,3 @@
              {preprocess, true}
             ]}.
 {validate_app_modules, true}.
-
-{deps,
-  [
-   %% {edown, ".*", {git, "git://github.com/sile/edown.git", {branch, "master"}}}
-  ]}.

+ 7 - 2
src/jsone.erl

@@ -46,6 +46,7 @@
               json_object_members/0,
               json_object_format_tuple/0,
               json_object_format_proplist/0,
+              json_object_format_map/0,
 
               encode_option/0,
               decode_option/0
@@ -59,20 +60,24 @@
 -type json_number()         :: number().
 -type json_string()         :: binary() | atom(). % NOTE: `decode/1' always returns `binary()' value
 -type json_array()          :: [json_value()].
--type json_object()         :: json_object_format_tuple() | json_object_format_proplist().
+-type json_object()         :: json_object_format_tuple()
+                             | json_object_format_proplist()
+                             | json_object_format_map().
 -type json_object_members() :: [{json_string(), json_value()}].
 
+-type json_object_format_map() :: map().
 -type json_object_format_tuple() :: {json_object_members()}.
 -type json_object_format_proplist() :: [{}] | json_object_members().
 
 -type encode_option() :: native_utf8.
 %% native_utf8: Encodes UTF-8 characters as a human-readable(non-escaped) string
 
--type decode_option() :: {object_format, tuple | proplist}.
+-type decode_option() :: {object_format, tuple | proplist | map}.
 %% object_format: <br />
 %%  - Decoded JSON object format <br />
 %%  - `tuple': An object is decoded as `{[]}' if it is empty, otherwise `{[{Key, Value}]}'. <br />
 %%  - `proplist': An object is decoded as `[{}]' if it is empty, otherwise `[{Key, Value}]'. <br />
+%%  - `map': An object is decoded as `#{}' if it is empty, otherwise `#{Key => Value}'. <br />
 %%  - default: `tuple' <br />
 
 %%--------------------------------------------------------------------------------

+ 6 - 5
src/jsone_decode.erl

@@ -51,7 +51,7 @@
 
 -type decode_result() :: {ok, jsone:json_value(), Rest::binary()} | {error, {Reason::term(), [erlang:stack_item()]}}.
 
--record(decode_opt_v1, { object_format=tuple :: tuple | proplist}).
+-record(decode_opt_v1, { object_format=tuple :: tuple | proplist | map}).
 -define(OPT, #decode_opt_v1).
 -type opt() :: #decode_opt_v1{}.
 
@@ -127,7 +127,7 @@ object_value(<<$:, Bin/binary>>, Key, Members, Nexts, Buf, Opt) -> whitespace(Bi
 object_value(Bin,                Key, Members, Nexts, Buf, Opt) -> ?ERROR(object_value, [Bin, Key, Members, Nexts, Buf, Opt]).
 
 -spec object_next(binary(), jsone:json_object_members(), [next()], binary(), opt()) -> decode_result().
-object_next(<<$}, Bin/binary>>, Members, Nexts, Buf, Opt) -> next(Bin, make_object(lists:reverse(Members), Opt), Nexts, Buf, Opt);
+object_next(<<$}, Bin/binary>>, Members, Nexts, Buf, Opt) -> next(Bin, make_object(Members, Opt), Nexts, Buf, Opt);
 object_next(<<$,, Bin/binary>>, Members, Nexts, Buf, Opt) -> whitespace(Bin, {object_key, Members}, Nexts, Buf, Opt);
 object_next(Bin,                Members, Nexts, Buf, Opt) -> ?ERROR(object_next, [Bin, Members, Nexts, Buf, Opt]).
 
@@ -264,9 +264,10 @@ number_exponation_part(Bin, N, DecimalOffset, ExpSign, Exp, IsFirst, Nexts, Buf,
     ?ERROR(number_exponation_part, [Bin, N, DecimalOffset, ExpSign, Exp, IsFirst, Nexts, Buf, Opt]).
 
 -spec make_object(jsone:json_object_members(), opt()) -> jsone:json_object().
-make_object(Members, ?OPT{object_format = tuple}) -> {Members};
+make_object(Members, ?OPT{object_format = tuple}) -> {lists:reverse(Members)};
+make_object(Members, ?OPT{object_format = map})   -> maps:from_list(Members);
 make_object([],      _)                           -> [{}];
-make_object(Members, _)                           -> Members.
+make_object(Members, _)                           -> lists:reverse(Members).
 
 -spec parse_options([jsone:decode_option()]) -> opt().
 parse_options(Options) ->
@@ -274,5 +275,5 @@ parse_options(Options) ->
 
 -spec parse_option([jsone:decode_option()], opt()) -> opt().
 parse_option([], Opt) -> Opt;
-parse_option([{object_format,F}|T], Opt) when F =:= tuple; F =:= proplist ->
+parse_option([{object_format,F}|T], Opt) when F =:= tuple; F =:= proplist; F =:= map ->
     parse_option(T, Opt?OPT{object_format=F}).

+ 1 - 0
src/jsone_encode.erl

@@ -93,6 +93,7 @@ value(Value, Nexts, Buf, Opt) when ?IS_STR(Value)    -> string(Value, Nexts, Buf
 value({Value}, Nexts, Buf, Opt)                      -> object(Value, Nexts, Buf, Opt);
 value([{}], Nexts, Buf, Opt)                         -> object([], Nexts, Buf, Opt);
 value([{_, _}|_] = Value, Nexts, Buf, Opt)           -> object(Value, Nexts, Buf, Opt);
+value(Value, Nexts, Buf, Opt) when is_map(Value)     -> object(maps:to_list(Value), Nexts, Buf, Opt);
 value(Value, Nexts, Buf, Opt) when is_list(Value)    -> array(Value, Nexts, Buf, Opt);
 value(Value, Nexts, Buf, _)                          -> ?ERROR(value, [Value, Nexts, Buf]).
 

+ 16 - 0
test/jsone_decode_tests.erl

@@ -178,6 +178,22 @@ decode_test_() ->
               ?assertEqual({ok, {[]}, <<"">>}, jsone_decode:decode(<<"{ \t\r\n}">>)),
               ?assertEqual({ok, [{}], <<"">>}, jsone_decode:decode(<<"{}">>, [{object_format, proplist}]))
       end},
+     {"simple object: map",
+      fun () ->
+              Input    = <<"{\"1\":2,\"key\":\"value\"}">>,
+              Expected = #{<<"1">> => 2, <<"key">> => <<"value">>},
+              ?assertEqual({ok, Expected, <<"">>}, jsone_decode:decode(Input, [{object_format, map}]))
+      end},
+     {"empty object: map",
+      fun () ->
+              ?assertEqual({ok, #{}, <<"">>}, jsone_decode:decode(<<"{}">>, [{object_format, map}]))
+      end},
+     {"duplicated members: map",
+      fun () ->
+              Input    = <<"{\"1\":\"first\",\"1\":\"second\"}">>,
+              Expected = #{<<"1">> => <<"first">>}, % the first (leftmost) value is used
+              ?assertEqual({ok, Expected, <<"">>}, jsone_decode:decode(Input, [{object_format, map}]))
+      end},
      {"object: trailing comma is disallowed",
       fun () ->
               Input = <<"{\"1\":2, \"key\":\"value\", }">>,

+ 12 - 0
test/jsone_encode_tests.erl

@@ -116,6 +116,18 @@ encode_test_() ->
               ?assertEqual({ok, Expected}, jsone_encode:encode(Input1)),
               ?assertEqual({ok, Expected}, jsone_encode:encode(Input2))
       end},
+     {"simple object: map",
+      fun () ->
+              Input = #{<<"key">> => <<"value">>, <<"1">> => 2},
+              Expected = <<"{\"1\":2,\"key\":\"value\"}">>,
+              ?assertEqual({ok, Expected}, jsone_encode:encode(Input))
+      end},
+     {"empty object: map",
+      fun () ->
+              Input = #{},
+              Expected = <<"{}">>,
+              ?assertEqual({ok, Expected}, jsone_encode:encode(Input))
+      end},
      {"atom key is allowed",
       fun () ->
               Expected = <<"{\"key\":2}">>,