Browse Source

Merge branch 'master' into hipe

Takeru Ohta 9 years ago
parent
commit
078dd54ac2
15 changed files with 153 additions and 135 deletions
  1. 2 1
      .gitignore
  2. 9 24
      Makefile
  3. 21 7
      README.md
  4. 0 8
      doc/README.md
  5. 24 72
      doc/jsone.md
  6. BIN
      rebar
  7. 18 0
      rebar.config
  8. 1 0
      rebar.lock
  9. BIN
      rebar3
  10. 5 5
      src/jsone.app.src
  11. 14 2
      src/jsone.erl
  12. 1 1
      src/jsone_decode.erl
  13. 22 3
      src/jsone_encode.erl
  14. 14 12
      test/jsone_decode_tests.erl
  15. 22 0
      test/jsone_encode_tests.erl

+ 2 - 1
.gitignore

@@ -9,4 +9,5 @@ deps
 doc/*
 !doc/overview.edoc
 !doc/*.md
-.rebar
+.rebar
+_build

+ 9 - 24
Makefile

@@ -1,37 +1,22 @@
-APP=jsone
-
-DIALYZER_OPTS=-Werror_handling -Wrace_conditions -Wunmatched_returns
-
-all: compile xref eunit dialyze
-
-init:
-	@./rebar get-deps compile
+all: compile xref eunit dialyze edoc
 
 compile:
-	@./rebar compile skip_deps=true
+	@./rebar3 compile
 
 xref:
-	@./rebar xref skip_deps=true
+	@./rebar3 xref
 
 clean:
-	@./rebar clean skip_deps=true
+	@./rebar3 clean
 
 eunit:
-	@./rebar eunit skip_deps=true
+	@./rebar3 eunit
 
 edoc:
-	@./rebar doc skip_deps=true
+	@./rebar3 as doc edoc
 
 start: compile
-	erl -pz ebin deps/*/ebin \
-      -eval 'erlang:display({start_app, $(APP), application:start($(APP))}).'
-
-.dialyzer.plt:
-	touch .dialyzer.plt
-	dialyzer --build_plt --plt .dialyzer.plt --apps erts kernel stdlib
-
-dialyze: .dialyzer.plt compile
-	dialyzer --plt .dialyzer.plt -r ebin $(DIALYZER_OPTS)
+	@./rebar3 shell
 
-create_app:
-	@./rebar create-app appid=$(APP) skip_deps=true
+dialyze: compile
+	@./rebar3 dialyzer

+ 21 - 7
README.md

@@ -1,6 +1,8 @@
-jsone (0.3.3)
+jsone (1.0.0)
 =============
 
+[![hex.pm version](https://img.shields.io/hexpm/v/jsone.svg)](https://hex.pm/packages/jsone)
+
 An Erlang library for encoding, decoding [JSON](http://json.org/index.html) data.
 
 
@@ -54,7 +56,13 @@ Usage Example
 [1,2,3]
 
 > jsone:decode(<<"{\"1\":2}">>).
-{[{<<"1">>,2}]}
+#{<<"1">> => 2}
+
+> jsone:decode(<<"{\"1\":2}">>, [{object_format, tuple}]). % tuple format
+{[{<<"1">>, 2}]}
+
+> jsone:decode(<<"{\"1\":2}">>, [{object_format, proplist}]). % proplist format
+[{<<"1">>, 2}]
 
 > jsone:try_decode(<<"[1,2,3] \"next value\"">>). % try_decode/1 returns remaining (unconsumed binary)
 {ok,[1,2,3],<<" \"next value\"">>}
@@ -77,14 +85,16 @@ Usage Example
 > jsone:encode([1,2,3]).
 <<"[1,2,3]">>
 
-> jsone:encode({[{<<"key">>, <<"value">>}]}).
+> jsone:encode(#{<<"key">> => <<"value">>}).  % map format
+> jsone:encode({[{<<"key">>, <<"value">>}]}). % tuple format
+> jsone:encode([{<<"key">>, <<"value">>}]).  % proplist format
 <<"{\"key\":\"value\"}">>
 
-> jsone:encode({[{key, <<"value">>}]}). % atom key is allowed
+> jsone:encode(#{key => <<"value">>}). % atom key is allowed
 <<"{\"key\":\"value\"}">>
 
 % error: raises exception
-> jsone:encode({[{123, <<"value">>}]}). % non binary|atom key is not allowed
+> jsone:encode(#{123 => <<"value">>}). % non binary|atom key is not allowed
 ** exception error: bad argument
      in function  jsone_encode:object_members/3
         called as jsone_encode:object_members([{123,<<"value">>}],[],<<"{">>)
@@ -96,9 +106,13 @@ Usage Example
                               [[{123,<<"value">>}],[],<<"{">>],
                               [{line,138}]}]}}
 
+% 'object_key_type' option allows non-string object key
+> jsone:encode({[{123, <<"value">>}]}, [{object_key_type, scalar}]).
+<<"{\"123\":\"value\"}">>
+
 %% Pretty Print
-> Data = [true, {[{<<"1">>, 2}, {<<"array">>, [[[[1]]], {[{<<"ab">>, <<"cd">>}]}, false]}]}, null],
-> io:format("~s\n", jsone:encode(Data, [{indent, 1}, {space, 2}])).
+> Data = [true, #{<<"1">> => 2, <<"array">> => [[[[1]]], #{<<"ab">> => <<"cd">>}, false]}, null].
+> io:format("~s\n", [jsone:encode(Data, [{indent, 1}, {space, 2}])]).
 [
   true,
   {

+ 0 - 8
doc/README.md

@@ -1,11 +1,3 @@
 
 
 # The jsone application #
-
-
-## Modules ##
-
-
-<table width="100%" border="0" summary="list of modules">
-<tr><td><a href="jsone.md" class="module">jsone</a></td></tr></table>
-

+ 24 - 72
doc/jsone.md

@@ -6,10 +6,8 @@
 * [Function Index](#index)
 * [Function Details](#functions)
 
-
 JSON decoding/encoding module.
 
-
 <a name="types"></a>
 
 ## Data Types ##
@@ -20,49 +18,45 @@ JSON decoding/encoding module.
 ### <a name="type-decode_option">decode_option()</a> ###
 
 
-
 <pre><code>
 decode_option() = {object_format, tuple | proplist | map}
 </code></pre>
 
-
-
-  object_format: <br />
+ 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 />
+- default: `map` <br />
 
 
 
 ### <a name="type-encode_option">encode_option()</a> ###
 
 
-
 <pre><code>
-encode_option() = native_utf8 | {float_format, [<a href="#type-float_format_option">float_format_option()</a>]} | {space, non_neg_integer()} | {indent, non_neg_integer()}
+encode_option() = native_utf8 | {float_format, [<a href="#type-float_format_option">float_format_option()</a>]} | {object_key_type, string | scalar | value} | {space, non_neg_integer()} | {indent, non_neg_integer()}
 </code></pre>
 
-
-
-
 `native_utf8`: <br />
 - Encodes UTF-8 characters as a human-readable(non-escaped) string <br />
 
-
-
 `{float_format, Optoins}`:
 - Encodes a `float()` value in the format which specified by `Options` <br />
 - default: `[{scientific, 20}]` <br />
 
-
+`object_key_type`:
+- Allowable object key type <br />
+- `string`: Only string values are allowed (i.e. `json_string()` type) <br />
+- `scalar`: In addition to `string`, following values are allowed: nulls, booleans, numerics (i.e. `json_scalar()` type) <br />
+- `value`: Any json compatible values are allowed (i.e. `json_value()` type) <br />
+- default: `string` <br />
+- NOTE: If `scalar` or `value` option is specified, non `json_string()` key will be automatically converted to a `binary()` value (e.g. `1` => `<<"1">>`, `#{}` => `<<"{}">>`) <br />
 
 `{space, N}`: <br />
 - Inserts `N` spaces after every commna and colon <br />
 - default: `0` <br />
 
-
 `{indent, N}`: <br />
 - Inserts a newline and `N` spaces for each level of indentation <br />
 - default: `0` <br />
@@ -72,29 +66,19 @@ encode_option() = native_utf8 | {float_format, [<a href="#type-float_format_opti
 ### <a name="type-float_format_option">float_format_option()</a> ###
 
 
-
 <pre><code>
 float_format_option() = {scientific, Decimals::0..249} | {decimals, Decimals::0..253} | compact
 </code></pre>
 
-
-
-
 `scientific`: <br />
 - The float will be formatted using scientific notation with `Decimals` digits of precision. <br />
 
-
-
 `decimals`: <br />
 - The encoded string will contain at most `Decimals` number of digits past the decimal point. <br />
 - If `compact` is provided the trailing zeros at the end of the string are truncated. <br />
 
-
-
 For more details, see [erlang:flaot_to_list/2](http://erlang.org/doc/man/erlang.md#float_to_list-2).
 
-
-
 ```
   > jsone:encode(1.23).
   <<"1.22999999999999998224e+00">>
@@ -110,11 +94,9 @@ For more details, see [erlang:flaot_to_list/2](http://erlang.org/doc/man/erlang.
 
 
 
-
 ### <a name="type-json_array">json_array()</a> ###
 
 
-
 <pre><code>
 json_array() = [<a href="#type-json_value">json_value()</a>]
 </code></pre>
@@ -122,11 +104,9 @@ json_array() = [<a href="#type-json_value">json_value()</a>]
 
 
 
-
 ### <a name="type-json_boolean">json_boolean()</a> ###
 
 
-
 <pre><code>
 json_boolean() = boolean()
 </code></pre>
@@ -134,11 +114,9 @@ json_boolean() = boolean()
 
 
 
-
 ### <a name="type-json_number">json_number()</a> ###
 
 
-
 <pre><code>
 json_number() = number()
 </code></pre>
@@ -146,11 +124,9 @@ json_number() = number()
 
 
 
-
 ### <a name="type-json_object">json_object()</a> ###
 
 
-
 <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> | <a href="#type-json_object_format_map">json_object_format_map()</a>
 </code></pre>
@@ -158,11 +134,9 @@ json_object() = <a href="#type-json_object_format_tuple">json_object_format_tupl
 
 
 
-
 ### <a name="type-json_object_format_map">json_object_format_map()</a> ###
 
 
-
 <pre><code>
 json_object_format_map() = #{}
 </code></pre>
@@ -170,11 +144,9 @@ json_object_format_map() = #{}
 
 
 
-
 ### <a name="type-json_object_format_proplist">json_object_format_proplist()</a> ###
 
 
-
 <pre><code>
 json_object_format_proplist() = [{}] | <a href="#type-json_object_members">json_object_members()</a>
 </code></pre>
@@ -182,11 +154,9 @@ json_object_format_proplist() = [{}] | <a href="#type-json_object_members">json_
 
 
 
-
 ### <a name="type-json_object_format_tuple">json_object_format_tuple()</a> ###
 
 
-
 <pre><code>
 json_object_format_tuple() = {<a href="#type-json_object_members">json_object_members()</a>}
 </code></pre>
@@ -194,11 +164,9 @@ json_object_format_tuple() = {<a href="#type-json_object_members">json_object_me
 
 
 
-
 ### <a name="type-json_object_members">json_object_members()</a> ###
 
 
-
 <pre><code>
 json_object_members() = [{<a href="#type-json_string">json_string()</a>, <a href="#type-json_value">json_value()</a>}]
 </code></pre>
@@ -206,30 +174,34 @@ json_object_members() = [{<a href="#type-json_string">json_string()</a>, <a href
 
 
 
+### <a name="type-json_scalar">json_scalar()</a> ###
+
+
+<pre><code>
+json_scalar() = <a href="#type-json_boolean">json_boolean()</a> | <a href="#type-json_number">json_number()</a> | <a href="#type-json_string">json_string()</a>
+</code></pre>
+
 
-### <a name="type-json_string">json_string()</a> ###
 
 
+### <a name="type-json_string">json_string()</a> ###
+
 
 <pre><code>
 json_string() = binary() | atom()
 </code></pre>
 
-
-
- NOTE: `decode/1` always returns `binary()` value
+NOTE: `decode/1` always returns `binary()` value
 
 
 
 ### <a name="type-json_value">json_value()</a> ###
 
 
-
 <pre><code>
 json_value() = <a href="#type-json_number">json_number()</a> | <a href="#type-json_string">json_string()</a> | <a href="#type-json_array">json_array()</a> | <a href="#type-json_object">json_object()</a> | <a href="#type-json_boolean">json_boolean()</a> | null
 </code></pre>
 
-
 <a name="index"></a>
 
 ## Function Index ##
@@ -246,32 +218,26 @@ json_value() = <a href="#type-json_number">json_number()</a> | <a href="#type-js
 
 ### decode/1 ###
 
-
 <pre><code>
 decode(Json::binary()) -&gt; <a href="#type-json_value">json_value()</a>
 </code></pre>
 <br />
 
 Equivalent to [`decode(Json, [])`](#decode-2).
+
 <a name="decode-2"></a>
 
 ### decode/2 ###
 
-
 <pre><code>
 decode(Json::binary(), Options::[<a href="#type-decode_option">decode_option()</a>]) -&gt; <a href="#type-json_value">json_value()</a>
 </code></pre>
 <br />
 
-
 Decodes an erlang term from json text (a utf8 encoded binary)
 
-
-
 Raises an error exception if input is not valid json
 
-
-
 ```
   > jsone:decode(<<"1">>, []).
   1
@@ -286,32 +252,26 @@ Raises an error exception if input is not valid json
 
 ### encode/1 ###
 
-
 <pre><code>
 encode(JsonValue::<a href="#type-json_value">json_value()</a>) -&gt; binary()
 </code></pre>
 <br />
 
 Equivalent to [`encode(JsonValue, [])`](#encode-2).
+
 <a name="encode-2"></a>
 
 ### encode/2 ###
 
-
 <pre><code>
 encode(JsonValue::<a href="#type-json_value">json_value()</a>, Options::[<a href="#type-encode_option">encode_option()</a>]) -&gt; binary()
 </code></pre>
 <br />
 
-
 Encodes an erlang term into json text (a utf8 encoded binary)
 
-
-
 Raises an error exception if input is not an instance of type `json_value()`
 
-
-
 ```
   > jsone:encode([1, null, 2]).
   <<"[1,null,2]">>
@@ -326,28 +286,24 @@ Raises an error exception if input is not an instance of type `json_value()`
 
 ### try_decode/1 ###
 
-
 <pre><code>
 try_decode(Json::binary()) -&gt; {ok, <a href="#type-json_value">json_value()</a>, Remainings::binary()} | {error, {Reason::term(), [<a href="erlang.md#type-stack_item">erlang:stack_item()</a>]}}
 </code></pre>
 <br />
 
 Equivalent to [`try_decode(Json, [])`](#try_decode-2).
+
 <a name="try_decode-2"></a>
 
 ### try_decode/2 ###
 
-
 <pre><code>
 try_decode(Json::binary(), Options::[<a href="#type-decode_option">decode_option()</a>]) -&gt; {ok, <a href="#type-json_value">json_value()</a>, Remainings::binary()} | {error, {Reason::term(), [<a href="erlang.md#type-stack_item">erlang:stack_item()</a>]}}
 </code></pre>
 <br />
 
-
 Decodes an erlang term from json text (a utf8 encoded binary)
 
-
-
 ```
   > jsone:try_decode(<<"[1,2,3] \"next value\"">>, []).
   {ok,[1,2,3],<<" \"next value\"">>}
@@ -361,28 +317,24 @@ Decodes an erlang term from json text (a utf8 encoded binary)
 
 ### try_encode/1 ###
 
-
 <pre><code>
 try_encode(JsonValue::<a href="#type-json_value">json_value()</a>) -&gt; {ok, binary()} | {error, {Reason::term(), [<a href="erlang.md#type-stack_item">erlang:stack_item()</a>]}}
 </code></pre>
 <br />
 
 Equivalent to [`try_encode(JsonValue, [])`](#try_encode-2).
+
 <a name="try_encode-2"></a>
 
 ### try_encode/2 ###
 
-
 <pre><code>
 try_encode(JsonValue::<a href="#type-json_value">json_value()</a>, Options::[<a href="#type-encode_option">encode_option()</a>]) -&gt; {ok, binary()} | {error, {Reason::term(), [<a href="erlang.md#type-stack_item">erlang:stack_item()</a>]}}
 </code></pre>
 <br />
 
-
 Encodes an erlang term into json text (a utf8 encoded binary)
 
-
-
 ```
   > jsone:try_encode([1, null, 2]).
   {ok,<<"[1,null,2]">>}

BIN
rebar


+ 18 - 0
rebar.config

@@ -26,3 +26,21 @@
              {preprocess, true}
             ]}.
 {validate_app_modules, true}.
+
+{dialyzer,
+ [
+  {warnings, [error_handling, race_conditions, unmatched_returns]}
+ ]}.
+
+{plugins, [rebar3_hex]}.
+
+{profiles,
+ [
+  {doc,
+   [
+    {deps,
+     [
+      {edown, {git, "git://github.com/uwiger/edown.git", {branch, "master"}}}
+     ]}
+   ]}
+ ]}.

+ 1 - 0
rebar.lock

@@ -0,0 +1 @@
+[].

BIN
rebar3


+ 5 - 5
src/jsone.app.src

@@ -2,11 +2,11 @@
 {application, jsone,
  [
   {description, "Erlang JSON Library"},
-  {vsn, git},
+  {vsn, "1.0.0"},
   {registered, []},
-  {applications, [
-                  kernel,
-                  stdlib
-                 ]},
+  {applications, [kernel, stdlib]},
+  {contributors, ["Takeru Ohta"]},
+  {licenses, ["MIT"]},
+  {links, [{"GitHub","https://github.com/sile/jsone"}]},
   {env, []}
  ]}.

+ 14 - 2
src/jsone.erl

@@ -47,6 +47,7 @@
               json_object_format_tuple/0,
               json_object_format_proplist/0,
               json_object_format_map/0,
+              json_scalar/0,
 
               encode_option/0,
               decode_option/0,
@@ -70,6 +71,8 @@
 -type json_object_format_tuple() :: {json_object_members()}.
 -type json_object_format_proplist() :: [{}] | json_object_members().
 
+-type json_scalar() :: json_boolean() | json_number() | json_string().
+
 -type float_format_option() :: {scientific, Decimals :: 0..249}
                              | {decimals, Decimals :: 0..253}
                              | compact.
@@ -101,15 +104,24 @@
 
 -type encode_option() :: native_utf8
                        | {float_format, [float_format_option()]}
+                       | {object_key_type, string | scalar | value}
                        | {space, non_neg_integer()}
                        | {indent, non_neg_integer()}.
 %% `native_utf8': <br />
 %% - Encodes UTF-8 characters as a human-readable(non-escaped) string <br />
 %%
-%% `{float_format, Optoins}`:
+%% `{float_format, Optoins}':
 %% - Encodes a `float()` value in the format which specified by `Options' <br />
 %% - default: `[{scientific, 20}]' <br />
 %%
+%% `object_key_type':
+%% - Allowable object key type <br />
+%% - `string': Only string values are allowed (i.e. `json_string()' type) <br />
+%% - `scalar': In addition to `string', following values are allowed: nulls, booleans, numerics (i.e. `json_scalar()' type) <br />
+%% - `value': Any json compatible values are allowed (i.e. `json_value()' type) <br />
+%% - default: `string' <br />
+%% - NOTE: If `scalar' or `value' option is specified, non `json_string()' key will be automatically converted to a `binary()' value (e.g. `1' => `<<"1">>', `#{}' => `<<"{}">>') <br />
+%%
 %% `{space, N}': <br />
 %% - Inserts `N' spaces after every commna and colon <br />
 %% - default: `0' <br />
@@ -124,7 +136,7 @@
 %%  - `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 />
+%%  - default: `map' <br />
 
 %%--------------------------------------------------------------------------------
 %% Exported Functions

+ 1 - 1
src/jsone_decode.erl

@@ -53,7 +53,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 | map}).
+-record(decode_opt_v1, { object_format=map :: tuple | proplist | map}).
 -define(OPT, #decode_opt_v1).
 -type opt() :: #decode_opt_v1{}.
 

+ 22 - 3
src/jsone_encode.erl

@@ -46,11 +46,13 @@
 -type encode_result() :: {ok, binary()} | {error, {Reason::term(), [erlang:stack_item()]}}.
 -type next() :: {array_values, [jsone:json_value()]}
               | {object_value, jsone:json_value(), jsone:json_object_members()}
-              | {object_members, jsone:json_object_members()}.
+              | {object_members, jsone:json_object_members()}
+              | {char, binary()}.
 
 -record(encode_opt_v1, {
           native_utf8 = false :: boolean(),
           float_format = [{scientific, 20}] :: [jsone:float_format_option()],
+          object_key_type = string :: string | scalar | value,
           space = 0 :: non_neg_integer(),
           indent = 0 :: non_neg_integer()
          }).
@@ -87,7 +89,9 @@ next(Level = [Next | Nexts], Buf, Opt) ->
             case Members of
                 [] -> object_members(Members, Nexts, Buf, Opt);
                 _  -> object_members(Members, Nexts, pp_newline_or_space(<<Buf/binary, $,>>, Level, Opt), Opt)
-            end
+            end;
+        {char, C} ->
+            next(Nexts, <<Buf/binary, C>>, Opt)
     end.
 
 -spec value(jsone:json_value(), [next()], binary(), opt()) -> encode_result().
@@ -110,6 +114,19 @@ string(<<Str/binary>>, Nexts, Buf, Opt) ->
 string(Str, Nexts, Buf, Opt) ->
     string(atom_to_binary(Str, utf8), Nexts, Buf, Opt).
 
+-spec object_key(jsone:json_value(), [next()], binary(), opt()) -> encode_result().
+object_key(Key, Nexts, Buf, Opt) when ?IS_STR(Key) ->
+    string(Key, Nexts, Buf, Opt);
+object_key(Key, Nexts, Buf, Opt = ?OPT{object_key_type = scalar}) when is_number(Key) ->
+    value(Key, [{char, $"} | Nexts], <<Buf/binary, $">>, Opt);
+object_key(Key, Nexts, Buf, Opt = ?OPT{object_key_type = value}) ->
+    case value(Key, [], <<>>, Opt) of
+        {error, Reason} -> {error, Reason};
+        {ok, BinaryKey} -> string(BinaryKey, Nexts, Buf, Opt)
+    end;
+object_key(Key, Nexts, Buf, Opt) ->
+    ?ERROR(object_key, [Key, Nexts, Buf, Opt]).
+
 -spec escape_string(binary(), [next()], binary(), opt()) -> encode_result().
 escape_string(<<"">>,                   Nexts, Buf, Opt) -> next(Nexts, <<Buf/binary, $">>, Opt);
 escape_string(<<$", Str/binary>>,       Nexts, Buf, Opt) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $">>, Opt);
@@ -173,7 +190,7 @@ object(Members, Nexts, Buf, Opt) ->
 
 -spec object_members(jsone:json_object_members(), [next()], binary(), opt()) -> encode_result().
 object_members([],                             Nexts, Buf, Opt)        -> next(Nexts, <<(pp_newline(Buf, Nexts, Opt))/binary, $}>>, Opt);
-object_members([{Key, Value} | Xs], Nexts, Buf, Opt) when ?IS_STR(Key) -> string(Key, [{object_value, Value, Xs} | Nexts], Buf, Opt);
+object_members([{Key, Value} | Xs], Nexts, Buf, Opt)                   -> object_key(Key, [{object_value, Value, Xs} | Nexts], Buf, Opt);
 object_members(Arg, Nexts, Buf, Opt)                                   -> ?ERROR(object_members, [Arg, Nexts, Buf, Opt]).
 
 -spec object_value(jsone:json_value(), jsone:json_object_members(), [next()], binary(), opt()) -> encode_result().
@@ -212,5 +229,7 @@ parse_option([{space, N}|T], Opt) when is_integer(N), N >= 0 ->
     parse_option(T, Opt?OPT{space = N});
 parse_option([{indent, N}|T], Opt) when is_integer(N), N >= 0 ->
     parse_option(T, Opt?OPT{indent = N});
+parse_option([{object_key_type, Type}|T], Opt) when Type =:= string; Type =:= scalar; Type =:= value ->
+    parse_option(T, Opt?OPT{object_key_type = Type});
 parse_option(List, Opt) ->
     error(badarg, [List, Opt]).

+ 14 - 12
test/jsone_decode_tests.erl

@@ -162,28 +162,30 @@ decode_test_() ->
      {"simple object",
       fun () ->
               Input    = <<"{\"1\":2,\"key\":\"value\"}">>,
+              Expected = #{<<"1">> => 2, <<"key">> => <<"value">>},
+              ?assertEqual({ok, Expected, <<"">>}, jsone_decode:decode(Input)), % `map' is the default format
+              ?assertEqual({ok, Expected, <<"">>}, jsone_decode:decode(Input, [{object_format, map}]))
+      end},
+     {"simple object: tuple or proplist",
+      fun () ->
+              Input    = <<"{\"1\":2,\"key\":\"value\"}">>,
               Expected = {[{<<"1">>, 2},{<<"key">>, <<"value">>}]},
-              ?assertEqual({ok, Expected, <<"">>}, jsone_decode:decode(Input)),
+              ?assertEqual({ok, Expected, <<"">>}, jsone_decode:decode(Input, [{object_format, tuple}])),
               ?assertEqual({ok, element(1, Expected), <<"">>}, jsone_decode:decode(Input, [{object_format, proplist}]))
       end},
      {"object: contains whitespaces",
       fun () ->
               Input    = <<"{  \"1\" :\t 2,\n\r\"key\" :   \n  \"value\"}">>,
-              Expected = {[{<<"1">>, 2},{<<"key">>, <<"value">>}]},
+              Expected = #{<<"1">> => 2, <<"key">> => <<"value">>},
               ?assertEqual({ok, Expected, <<"">>}, jsone_decode:decode(Input))
       end},
      {"empty object",
       fun () ->
-              ?assertEqual({ok, {[]}, <<"">>}, jsone_decode:decode(<<"{}">>)),
-              ?assertEqual({ok, {[]}, <<"">>}, jsone_decode:decode(<<"{ \t\r\n}">>)),
+              ?assertEqual({ok, #{}, <<"">>}, jsone_decode:decode(<<"{}">>)),
+              ?assertEqual({ok, #{}, <<"">>}, jsone_decode:decode(<<"{ \t\r\n}">>)),
+              ?assertEqual({ok, {[]}, <<"">>}, jsone_decode:decode(<<"{}">>, [{object_format, tuple}])),
               ?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}]))
@@ -198,7 +200,7 @@ decode_test_() ->
       fun () ->
               Input = <<"{\"1\":2, \"key\":\"value\", }">>,
               io:format("~p\n", [catch jsone_decode:decode(Input)]),
-              ?assertMatch({error, {badarg, _}}, jsone_decode:decode(Input))
+              ?assertMatch({error, {badarg, _}}, jsone_decode:decode(Input, [{object_format, tuple}]))
       end},
      {"object: missing comma",
       fun () ->
@@ -230,7 +232,7 @@ decode_test_() ->
      {"compound data",
       fun () ->
               Input    = <<"  [true, {\"1\" : 2, \"array\":[[[[1]]], {\"ab\":\"cd\"}, false]}, null]   ">>,
-              Expected = [true, {[{<<"1">>, 2}, {<<"array">>, [[[[1]]], {[{<<"ab">>, <<"cd">>}]}, false]}]}, null],
+              Expected = [true, #{<<"1">> => 2, <<"array">> => [[[[1]]], #{<<"ab">> => <<"cd">>}, false]}, null],
               ?assertEqual({ok, Expected, <<"   ">>}, jsone_decode:decode(Input))
       end}
     ].

+ 22 - 0
test/jsone_encode_tests.erl

@@ -142,6 +142,28 @@ encode_test_() ->
               Expected = <<"{\"key\":2}">>,
               ?assertEqual({ok, Expected}, jsone_encode:encode({[{key, 2}]}))
       end},
+     {"object_key_type option",
+      fun () ->
+              %% key: atom
+              ?assertEqual({ok, <<"{\"a\":2}">>}, jsone_encode:encode(#{a => 2}, [{object_key_type, string}])), % OK
+              ?assertEqual({ok, <<"{\"a\":2}">>}, jsone_encode:encode(#{a => 2}, [{object_key_type, scalar}])), % OK
+              ?assertEqual({ok, <<"{\"a\":2}">>}, jsone_encode:encode(#{a => 2}, [{object_key_type, value}])),  % OK
+
+              %% key: number
+              ?assertMatch({error, {badarg, _}},  jsone_encode:encode(#{1 => 2}, [{object_key_type, string}])), % NG
+              ?assertEqual({ok, <<"{\"1\":2}">>}, jsone_encode:encode(#{1 => 2}, [{object_key_type, scalar}])), % OK
+              ?assertEqual({ok, <<"{\"1\":2}">>}, jsone_encode:encode(#{1 => 2}, [{object_key_type, value}])),  % OK
+
+              %% key: array
+              ?assertMatch({error, {badarg, _}},    jsone_encode:encode(#{[1] => 2}, [{object_key_type, string}])), % NG
+              ?assertMatch({error, {badarg, _}},    jsone_encode:encode(#{[1] => 2}, [{object_key_type, scalar}])), % NG
+              ?assertEqual({ok, <<"{\"[1]\":2}">>}, jsone_encode:encode(#{[1] => 2}, [{object_key_type, value}])),  % OK
+
+              %% key: object
+              ?assertMatch({error, {badarg, _}},    jsone_encode:encode(#{#{} => 2}, [{object_key_type, string}])), % NG
+              ?assertMatch({error, {badarg, _}},    jsone_encode:encode(#{#{} => 2}, [{object_key_type, scalar}])), % NG
+              ?assertEqual({ok, <<"{\"{}\":2}">>}, jsone_encode:encode(#{#{} => 2}, [{object_key_type, value}]))    % OK
+      end},
      {"non binary object member key is disallowed",
       fun () ->
               ?assertMatch({error, {badarg, _}}, jsone_encode:encode({[{1, 2}]})),