sile / jsone

Erlang JSON library
MIT License
291 stars 71 forks source link

encode ip address/cidr values into json #49

Closed ghost closed 3 years ago

ghost commented 4 years ago

This change supports encoding ip addresses and CIDR values into json. I added these changes to allow encoding postgres database's inet/cidr column values into json.

codecov-commenter commented 4 years ago

Codecov Report

Merging #49 into master will increase coverage by 0.61%. The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #49      +/-   ##
==========================================
+ Coverage   88.72%   89.34%   +0.61%     
==========================================
  Files           3        3              
  Lines         275      291      +16     
==========================================
+ Hits          244      260      +16     
  Misses         31       31              
Impacted Files Coverage Δ
src/jsone_encode.erl 87.01% <100.00%> (+1.50%) :arrow_up:

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update 2ebbe23...558b08c. Read the comment docs.

sile commented 4 years ago

Thank you for your PR but I'm wondering how many users want this feature. If this is a feature specific to your need, I'd like to implement a more generic feature, like map_unknown_value option below, instead and enable encoding IP addresses by using it.

diff --git a/src/jsone.erl b/src/jsone.erl
index 11878ec..2f2e40d 100644
--- a/src/jsone.erl
+++ b/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 />
diff --git a/src/jsone_encode.erl b/src/jsone_encode.erl
index 6cdf98a..4ce79be 100644
--- a/src/jsone_encode.erl
+++ b/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, Value1} -> value(Value1, 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_known_value, Fun}|T], Opt) when is_function(Fun) ->
+    parse_option(T, Opt?OPT{map_value=Fun});
 parse_option(List, Opt) ->
     error(badarg, [List, Opt]).
> jsone:encode([{1,2,3,4}], [{map_value, fun ({_,_,_,_} = Ip4) -> {ok, list_to_binary(inet:ntoa(Ip4))} end}]).
<<"[\"1,2,3,4\"]">>

What do you think of it?

galdor commented 3 years ago

Another possibility would be to use {Type, Value} tuples, e.g. {ipv4_address, {192, 168, 0, 1}}. In general, relying on the shape of tuples to infer the type of data they represent can cause interesting issues. But I like the function proposed by @sile, it would be nice to be able to support various kind of data types without having to patch jsone.

sile commented 3 years ago

@galdor Thank you for your comment! I'll create a PR to add the map_value feature in the near future.

sile commented 3 years ago

Let me close this PR since I implemented the map_unknown_value option described above in #51.