Переглянути джерело

fix(json): convert a proplists to ejson instead of map

JianBo He 5 роки тому
батько
коміт
600b1055f3
2 змінених файлів з 200 додано та 7 видалено
  1. 4 7
      src/emqx_json.erl
  2. 196 0
      test/props/prop_emqx_json.erl

+ 4 - 7
src/emqx_json.erl

@@ -103,16 +103,13 @@ safe_decode(Json, Opts) ->
           , from_ejson/1
           ]}).
 
-to_ejson([[{_,_}|_]|_] = L) ->
-    [to_ejson(E) || E <- L];
 to_ejson([{_, _}|_] = L) ->
-    lists:foldl(
-      fun({Name, Value}, Acc) ->
-        Acc#{Name => to_ejson(Value)}
-      end, #{}, L);
+    {[{K, to_ejson(V)} || {K, V} <- L ]};
+to_ejson(L) when is_list(L) ->
+    [to_ejson(E) || E <- L];
 to_ejson(T) -> T.
 
-from_ejson([{_}|_] = L) ->
+from_ejson(L) when is_list(L) ->
     [from_ejson(E) || E <- L];
 from_ejson({L}) ->
     [{Name, from_ejson(Value)} || {Name, Value} <- L];

+ 196 - 0
test/props/prop_emqx_json.erl

@@ -0,0 +1,196 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%%     http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+
+-module(prop_emqx_json).
+
+-import(emqx_json,
+         [ decode/1
+         , decode/2
+         , encode/1
+         , safe_decode/1
+         , safe_decode/2
+         , safe_encode/1
+         ]).
+
+-include_lib("proper/include/proper.hrl").
+
+%%--------------------------------------------------------------------
+%% Properties
+%%--------------------------------------------------------------------
+
+prop_json_basic() ->
+    ?FORALL(T, json_basic(),
+            begin
+                {ok, J} = safe_encode(T),
+                {ok, T} = safe_decode(J),
+                T = decode(encode(T)),
+                true
+            end).
+
+prop_json_basic_atom() ->
+    ?FORALL(T0,  latin_atom(),
+            begin
+                T = atom_to_binary(T0, utf8),
+                {ok, J} = safe_encode(T0),
+                {ok, T} = safe_decode(J),
+                T = decode(encode(T0)),
+                true
+            end).
+
+prop_object_proplist_to_proplist() ->
+    ?FORALL(T, json_object(),
+            begin
+                {ok, J} = safe_encode(T),
+                {ok, T} = safe_decode(J),
+                T = decode(encode(T)),
+                true
+            end).
+
+prop_object_map_to_map() ->
+    ?FORALL(T, json_object_map(),
+            begin
+                {ok, J} = safe_encode(T),
+                {ok, T} = safe_decode(J, [return_maps]),
+                T = decode(encode(T), [return_maps]),
+                true
+            end).
+
+%% The duplicated key will be overriden
+prop_object_proplist_to_map() ->
+    ?FORALL(T0, json_object(),
+           begin
+               T = to_map(T0),
+               {ok, J} = safe_encode(T0),
+               {ok, T} = safe_decode(J, [return_maps]),
+               T = decode(encode(T0), [return_maps]),
+               true
+           end).
+
+prop_object_map_to_proplist() ->
+    ?FORALL(T0, json_object_map(),
+           begin
+               %% jiffy encode a map with descending order, that is,
+               %% it is opposite with maps traversal sequence
+               %% see: the `to_list` implementation
+               T = to_list(T0),
+               {ok, J} = safe_encode(T0),
+               {ok, T} = safe_decode(J),
+               T = decode(encode(T0)),
+               true
+           end).
+
+prop_safe_encode() ->
+    ?FORALL(T, invalid_json_term(),
+           begin
+               {error, _} = safe_encode(T), true
+           end).
+
+prop_safe_decode() ->
+    ?FORALL(T, invalid_json_str(),
+           begin
+               {error, _} = safe_decode(T), true
+           end).
+
+%%--------------------------------------------------------------------
+%% Helpers
+%%--------------------------------------------------------------------
+
+to_map([{_, _}|_] = L) ->
+    lists:foldl(
+      fun({Name, Value}, Acc) ->
+        Acc#{Name => to_map(Value)}
+      end, #{}, L);
+to_map(L) when is_list(L) ->
+    [to_map(E) || E <- L];
+to_map(T) -> T.
+
+to_list(L) when is_list(L) ->
+    [to_list(E) || E <- L];
+to_list(M) when is_map(M) ->
+    maps:fold(
+      fun(K, V, Acc) ->
+        [{K, to_list(V)}|Acc]
+      end, [], M);
+to_list(T) -> T.
+
+%%--------------------------------------------------------------------
+%% Generators (https://tools.ietf.org/html/rfc8259)
+%%--------------------------------------------------------------------
+
+%% true, false, null, and number(), string()
+json_basic() ->
+    oneof([true, false, null, number(), json_string()]).
+
+latin_atom() ->
+    ?LET(L, list(latin_char()), list_to_atom(L)).
+
+latin_char() ->
+    L = lists:concat([lists:seq($0, $9),
+                      lists:seq($a, $z),
+                      lists:seq($A, $Z)]),
+    oneof(L).
+
+json_string() -> utf8().
+
+json_object() ->
+    oneof([json_array_1(), json_object_1(), json_array_object_1(),
+           json_array_2(), json_object_2(), json_array_object_2()]).
+
+json_object_map() ->
+    ?LET(L, json_object(), to_map(L)).
+
+json_array_1() ->
+    list(json_basic()).
+
+json_array_2() ->
+    list([json_basic(), json_array_1()]).
+
+json_object_1() ->
+    list({json_key(), json_basic()}).
+
+json_object_2() ->
+    list({json_key(), oneof([json_basic(),
+                             json_array_1(),
+                             json_object_1()])}).
+
+json_array_object_1() ->
+    list(json_object_1()).
+
+json_array_object_2() ->
+    list(json_object_2()).
+
+%% @private
+json_key() ->
+    ?LET(K, latin_atom(), atom_to_binary(K, utf8)).
+
+invalid_json_term() ->
+    ?SUCHTHAT(T, tuple(), (tuple_size(T) /= 1)).
+
+invalid_json_str() ->
+    ?LET(T, json_object_2(), chaos(encode(T))).
+
+%% @private
+chaos(S) when is_binary(S) ->
+    T = [$\r, $\n, $", ${, $}, $[, $], $:, $,],
+    iolist_to_binary(chaos(binary_to_list(S), 100, T)).
+
+chaos(S, 0, _) ->
+    S;
+chaos(S, N, T) ->
+    I = rand:uniform(length(S)),
+    {L1, L2} = lists:split(I, S),
+    chaos(lists:flatten([L1, lists:nth(rand:uniform(length(T)), T), L2]), N-1, T).
+