Просмотр исходного кода

Merge pull request #27 from project-fifo/undefined_as_null-decoder

Add undefined_as_null to decoder
Takeru Ohta 7 лет назад
Родитель
Сommit
8f2970cdfc
5 измененных файлов с 36 добавлено и 13 удалено
  1. 1 0
      README.md
  2. 15 6
      doc/jsone.md
  3. 9 6
      src/jsone.erl
  4. 6 1
      src/jsone_decode.erl
  5. 5 0
      test/jsone_decode_tests.erl

+ 1 - 0
README.md

@@ -176,6 +176,7 @@ Erlang                  JSON             Erlang
 =================================================================================================
 
 null                   -> null                       -> null
+undefined              -> null                       -> undefined                  % undefined_as_null
 true                   -> true                       -> true
 false                  -> false                      -> false
 <<"abc">>              -> "abc"                      -> <<"abc">>

+ 15 - 6
doc/jsone.md

@@ -15,6 +15,18 @@ JSON decoding/encoding module.
 
 
 
+### <a name="type-common_option">common_option()</a> ###
+
+
+<pre><code>
+common_option() = undefined_as_null
+</code></pre>
+
+`undefined_as_null`: <br />
+- Treats `undefined` in Erlang as the conversion target for `null` in JSON. This means that `undefined` will be encoded to `null` and `null` will be decoded to `undefined`<br />
+
+
+
 ### <a name="type-datetime_encode_format">datetime_encode_format()</a> ###
 
 
@@ -60,7 +72,7 @@ datetime_format() = iso8601
 
 
 <pre><code>
-decode_option() = {object_format, tuple | proplist | map} | {allow_ctrl_chars, boolean()} | {keys, binary | atom | existing_atom | attempt_atom}
+decode_option() = {object_format, tuple | proplist | map} | {allow_ctrl_chars, boolean()} | {keys, binary | atom | existing_atom | attempt_atom} | <a href="#type-common_option">common_option()</a>
 </code></pre>
 
 `object_format`: <br />
@@ -92,7 +104,7 @@ binary string if fails find one.
 
 
 <pre><code>
-encode_option() = native_utf8 | canonical_form | {float_format, [<a href="#type-float_format_option">float_format_option()</a>]} | {datetime_format, <a href="#type-datetime_encode_format">datetime_encode_format()</a>} | {object_key_type, string | scalar | value} | {space, non_neg_integer()} | {indent, non_neg_integer()} | undefined_as_null
+encode_option() = native_utf8 | canonical_form | {float_format, [<a href="#type-float_format_option">float_format_option()</a>]} | {datetime_format, <a href="#type-datetime_encode_format">datetime_encode_format()</a>} | {object_key_type, string | scalar | value} | {space, non_neg_integer()} | {indent, non_neg_integer()} | <a href="#type-common_option">common_option()</a>
 </code></pre>
 
 `native_utf8`: <br />
@@ -125,9 +137,6 @@ encode_option() = native_utf8 | canonical_form | {float_format, [<a href="#type-
 - Inserts a newline and `N` spaces for each level of indentation <br />
 - default: `0` <br />
 
-`undefined_as_null`: <br />
-- Encodes atom `undefined` as null value <br />
-
 
 
 ### <a name="type-float_format_option">float_format_option()</a> ###
@@ -318,7 +327,7 @@ A simple example is worth a thousand words.
 
 
 <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 | <a href="#type-json_term">json_term()</a>
+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 | undefined | <a href="#type-json_term">json_term()</a>
 </code></pre>
 
 

+ 9 - 6
src/jsone.erl

@@ -60,7 +60,7 @@
 %%--------------------------------------------------------------------------------
 %% Types & Macros
 %%--------------------------------------------------------------------------------
--type json_value()          :: json_number() | json_string() | json_array() | json_object() | json_boolean() | null | json_term().
+-type json_value()          :: json_number() | json_string() | json_array() | json_object() | json_boolean() | null | undefined | json_term().
 -type json_boolean()        :: boolean().
 -type json_number()         :: number().
 -type json_string()         :: binary() | atom() | calendar:datetime(). % NOTE: `decode/1' always returns `binary()' value
@@ -183,6 +183,11 @@
 -type timezone() :: utc | local | utc_offset_seconds().
 -type utc_offset_seconds() :: -86399..86399.
 
+-type common_option() :: undefined_as_null.
+%%
+%% `undefined_as_null': <br />
+%% - Treats `undefined' in Erlang as the conversion target for `null' in JSON. This means that `undefined' will be encoded to `null' and `null' will be decoded to `undefined'<br />
+
 -type encode_option() :: native_utf8
                        | canonical_form
                        | {float_format, [float_format_option()]}
@@ -190,7 +195,7 @@
                        | {object_key_type, string | scalar | value}
                        | {space, non_neg_integer()}
                        | {indent, non_neg_integer()}
-                       | undefined_as_null.
+                       | common_option().
 %% `native_utf8': <br />
 %% - Encodes UTF-8 characters as a human-readable(non-escaped) string <br />
 %%
@@ -220,13 +225,11 @@
 %% `{indent, N}': <br />
 %% - Inserts a newline and `N' spaces for each level of indentation <br />
 %% - default: `0' <br />
-%%
-%% `undefined_as_null': <br />
-%% - Encodes atom `undefined' as null value <br />
 
 -type decode_option() :: {object_format, tuple | proplist | map}
                        | {allow_ctrl_chars, boolean()}
-                       | {'keys', 'binary' | 'atom' | 'existing_atom' | 'attempt_atom'}.
+                       | {'keys', 'binary' | 'atom' | 'existing_atom' | 'attempt_atom'}
+                       | common_option().
 %% `object_format': <br />
 %% - Decoded JSON object format <br />
 %% - `tuple': An object is decoded as `{[]}' if it is empty, otherwise `{[{Key, Value}]}'. <br />

+ 6 - 1
src/jsone_decode.erl

@@ -67,7 +67,8 @@
         {
           object_format=?DEFAULT_OBJECT_FORMAT :: tuple | proplist | map,
           allow_ctrl_chars=false :: boolean(),
-          keys=binary :: 'binary' | 'atom' | 'existing_atom' | 'attempt_atom'
+          keys=binary :: 'binary' | 'atom' | 'existing_atom' | 'attempt_atom',
+          undefined_as_null=false :: boolean()
         }).
 -define(OPT, #decode_opt_v2).
 -type opt() :: #decode_opt_v2{}.
@@ -116,6 +117,8 @@ whitespace(<<Bin/binary>>,      Next, Nexts, Buf, Opt) ->
 -spec value(binary(), [next()], binary(), opt()) -> decode_result().
 value(<<"false", Bin/binary>>, Nexts, Buf, Opt) -> next(Bin, false, Nexts, Buf, Opt);
 value(<<"true", Bin/binary>>, Nexts, Buf, Opt)  -> next(Bin, true, Nexts, Buf, Opt);
+value(<<"null", Bin/binary>>, Nexts, Buf,
+      Opt = ?OPT{undefined_as_null = true})     -> next(Bin, undefined, Nexts, Buf, Opt);
 value(<<"null", Bin/binary>>, Nexts, Buf, Opt)  -> next(Bin, null, Nexts, Buf, Opt);
 value(<<$[, Bin/binary>>, Nexts, Buf, Opt)      -> whitespace(Bin, array, Nexts, Buf, Opt);
 value(<<${, Bin/binary>>, Nexts, Buf, Opt)      -> whitespace(Bin, object, Nexts, Buf, Opt);
@@ -301,5 +304,7 @@ parse_option([{allow_ctrl_chars,B}|T], Opt) when is_boolean(B) ->
 parse_option([{keys, K}|T], Opt)
   when K =:= binary; K =:= atom; K =:= existing_atom; K =:= attempt_atom ->
     parse_option(T, Opt?OPT{keys = K});
+parse_option([undefined_as_null|T], Opt) ->
+    parse_option(T, Opt?OPT{undefined_as_null = true});
 parse_option(List, Opt) ->
     error(badarg, [List, Opt]).

+ 5 - 0
test/jsone_decode_tests.erl

@@ -290,5 +290,10 @@ decode_test_() ->
               Input    = <<"  [true, {\"1\" : 2, \"array\":[[[[1]]], {\"ab\":\"cd\"}, false]}, null]   ">>,
               Expected = [true, ?OBJ2(<<"1">>, 2, <<"array">>, [[[[1]]], ?OBJ1(<<"ab">>, <<"cd">>), false]), null],
               ?assertEqual({ok, Expected, <<"   ">>}, jsone_decode:decode(Input))
+      end},
+     {"undefined_as_null option",
+      fun() ->
+              ?assertEqual({ok, undefined, <<>>},  jsone_decode:decode(<<"null">>,[undefined_as_null])), % OK
+              ?assertEqual({ok, null, <<>>},       jsone_decode:decode(<<"null">>,[])) % OK
       end}
     ].