Browse Source

Merge pull request #51 from sile/map-unknown-value-option

Add `map_unknown_value` encoding option
Takeru Ohta 4 years ago
parent
commit
d66a75156b
4 changed files with 33 additions and 5 deletions
  1. 5 3
      doc/jsone.md
  2. 4 0
      src/jsone.erl
  3. 13 2
      src/jsone_encode.erl
  4. 11 0
      test/jsone_encode_tests.erl

+ 5 - 3
doc/jsone.md

@@ -120,7 +120,7 @@ the last such instance.
 
 
 <pre><code>
-encode_option() = native_utf8 | native_forward_slash | 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>
+encode_option() = native_utf8 | native_forward_slash | 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()} | {map_unknown_value, fun((term()) -&gt; {ok, <a href="#type-json_value">json_value()</a>} | error)} | <a href="#type-common_option">common_option()</a>
 </code></pre>
 
 `native_utf8`: <br />
@@ -156,6 +156,9 @@ encode_option() = native_utf8 | native_forward_slash | canonical_form | {float_f
 - Inserts a newline and `N` spaces for each level of indentation <br />
 - default: `0` <br />
 
+`{map_unknown_value, Fun}`: <br />
+- If specified, unknown values encountered during an encoding process are converted to `json_value()` by applying `Fun`.
+
 
 
 ### <a name="type-float_format_option">float_format_option()</a> ###
@@ -233,7 +236,7 @@ json_object() = <a href="#type-json_object_format_tuple">json_object_format_tupl
 
 
 <pre><code>
-json_object_format_map() = map()
+json_object_format_map() = #{}
 </code></pre>
 
 
@@ -525,4 +528,3 @@ Encodes an erlang term into json text (a utf8 encoded binary)
                                 [hoge,[{array_values,[2]}],<<"[1,">>],
                                 [{line,86}]}]}}
 ```
-

+ 4 - 0
src/jsone.erl

@@ -196,6 +196,7 @@
                        | {object_key_type, string | scalar | value}
                        | {space, non_neg_integer()}
                        | {indent, non_neg_integer()}
+                       | {map_unknown_value, fun ((term()) -> {ok, json_value()} | error)}
                        | common_option().
 %% `native_utf8': <br />
 %% - Encodes non ASCII UTF-8 characters as a human-readable(non-escaped) string <br />
@@ -229,6 +230,9 @@
 %% `{indent, N}': <br />
 %% - Inserts a newline and `N' spaces for each level of indentation <br />
 %% - default: `0' <br />
+%%
+%% `{map_unknown_value, Fun}`: <br />
+%% - If specified, unknown values encountered during an encoding process are converted to `json_value()` by applying `Fun'.
 
 -type decode_option() :: {object_format, tuple | proplist | map}
                        | {allow_ctrl_chars, boolean()}

+ 13 - 2
src/jsone_encode.erl

@@ -70,7 +70,8 @@
           object_key_type = string :: string | scalar | value,
           space = 0 :: non_neg_integer(),
           indent = 0 :: non_neg_integer(),
-          undefined_as_null = false :: boolean()
+          undefined_as_null = false :: boolean(),
+          map_unknown_value = undefined :: undefined | fun ((term()) -> {ok, jsone:json_value()} | error)
          }).
 -define(OPT, #encode_opt_v2).
 -type opt() :: #encode_opt_v2{}.
@@ -142,7 +143,15 @@ value([{{_,_,_},{_,_,_}}|_] = Value, Nexts, Buf, Opt)-> array(Value, Nexts, Buf,
 value([{_, _}|_] = Value, Nexts, Buf, Opt)           -> object(Value, Nexts, Buf, Opt);
 value(Value, Nexts, Buf, Opt) when ?IS_MAP(Value)    -> ?ENCODE_MAP(Value, Nexts, Buf, Opt);
 value(Value, Nexts, Buf, Opt) when is_list(Value)    -> array(Value, Nexts, Buf, Opt);
-value(Value, Nexts, Buf, Opt)                        -> ?ERROR(value, [Value, Nexts, Buf, Opt]).
+value(Value, Nexts, Buf, Opt)                        ->
+    case Opt?OPT.map_unknown_value of
+        undefined -> ?ERROR(value, [Value, Nexts, Buf, Opt]);
+        Fun       ->
+            case Fun(Value) of
+                error          -> ?ERROR(value, [Value, Nexts, Buf, Opt]);
+                {ok, NewValue} -> value(NewValue, Nexts, Buf, Opt)
+            end
+    end.
 
 -spec string(jsone:json_string(), [next()], binary(), opt()) -> encode_result().
 string(<<Str/binary>>, Nexts, Buf, Opt) ->
@@ -385,6 +394,8 @@ parse_option([{datetime_format, Fmt}|T], Opt) ->
     end;
 parse_option([undefined_as_null|T],Opt) ->
     parse_option(T, Opt?OPT{undefined_as_null = true});
+parse_option([{map_unknown_value, F}|T], Opt) when is_function(F, 1) ->
+    parse_option(T, Opt?OPT{map_unknown_value = F});
 parse_option(List, Opt) ->
     error(badarg, [List, Opt]).
 

+ 11 - 0
test/jsone_encode_tests.erl

@@ -292,6 +292,17 @@ encode_test_() ->
               ?assertEqual({ok, <<"{\n  \"a\":  1,\n  \"b\":  2\n}">>}, jsone_encode:encode(?OBJ2(a, 1, b, 2), [{indent, 2}, {space, 2}]))
       end},
 
+     %% `map_unknown_value` option
+     {"`map_unknown_value` option",
+      fun () ->
+              Input = [{1,2,3,4}],
+              MapFun = fun ({_,_,_,_} = Ip4) -> {ok, list_to_binary(inet:ntoa(Ip4))};
+                           (_)               -> error
+                       end,
+              Expected = <<"[\"1.2.3.4\"]">>,
+              ?assertEqual(Expected, jsone:encode(Input, [{map_unknown_value, MapFun}]))
+      end},
+
      %% Others
      {"compound data",
       fun () ->