From 600b1055f3e5a4deb76a8c5b53c530105a43c0f6 Mon Sep 17 00:00:00 2001 From: JianBo He Date: Fri, 19 Jun 2020 11:35:16 +0800 Subject: [PATCH] fix(json): convert a proplists to ejson instead of map --- src/emqx_json.erl | 11 +- test/props/prop_emqx_json.erl | 196 ++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 test/props/prop_emqx_json.erl diff --git a/src/emqx_json.erl b/src/emqx_json.erl index 847c91b34..92da203f4 100644 --- a/src/emqx_json.erl +++ b/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]; diff --git a/test/props/prop_emqx_json.erl b/test/props/prop_emqx_json.erl new file mode 100644 index 000000000..86f57a1d3 --- /dev/null +++ b/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). +