jsone_encode_tests.erl 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. %% Copyright (c) 2013-2014, Takeru Ohta <phjgt308@gmail.com>
  2. %% coding: latin-1
  3. -module(jsone_encode_tests).
  4. -include_lib("eunit/include/eunit.hrl").
  5. -ifdef('NO_MAP_TYPE').
  6. -define(OBJ0, {[]}).
  7. -define(OBJ1(K, V), {[{K, V}]}).
  8. -define(OBJ2(K1, V1, K2, V2), {[{K1, V1}, {K2, V2}]}).
  9. -define(OBJECT_FROM_LIST(List), List).
  10. -else.
  11. -define(OBJ0, #{}).
  12. -define(OBJ1(K, V), #{K => V}).
  13. -define(OBJ2(K1, V1, K2, V2), #{K1 => V1, K2 => V2}).
  14. -define(OBJECT_FROM_LIST(List), maps:from_list(List)).
  15. -endif.
  16. encode_test_() ->
  17. [
  18. %% Symbols
  19. {"false", fun() -> ?assertEqual({ok, <<"false">>}, jsone_encode:encode(false)) end},
  20. {"true", fun() -> ?assertEqual({ok, <<"true">>}, jsone_encode:encode(true)) end},
  21. {"null", fun() -> ?assertEqual({ok, <<"null">>}, jsone_encode:encode(null)) end},
  22. %% Numbers: Inline json term
  23. {"json",
  24. fun() ->
  25. ?assertEqual({ok, <<"{\"foo\":[1,2,3],\"bar\":\"", 195, 169, "ok\"}">>},
  26. jsone_encode:encode(?OBJ2(foo,
  27. {{json, ["[" | [$1, ",2", <<",3]">>]]}},
  28. <<"bar">>,
  29. {{json_utf8, [$", 233, "ok", $"]}}))),
  30. ?assertEqual({ok, <<"{\"foo\":[1,2,3],\"bar\":\"", 233, "ok\"}">>},
  31. jsone_encode:encode(?OBJ2(foo,
  32. {{json, ["[" | [$1, ",2", <<",3]">>]]}},
  33. <<"bar">>,
  34. {{json, [$", 233, "ok", $"]}}))),
  35. ?assertEqual({ok, <<"{\"json\":\"[1,2,3]\"}">>}, jsone_encode:encode([{json, <<"[1,2,3]">>}])),
  36. ?assertEqual({ok, <<"[[1,2,3]]">>}, jsone_encode:encode([{{json, <<"[1,2,3]">>}}])),
  37. %% Errors
  38. ?assertMatch({error, {{invalid_json_utf8, _, _}, _}}, jsone_encode:encode({{json_utf8, <<200, 83, 1>>}})),
  39. ?assertMatch({error, {{invalid_json_utf8, _, _}, _}}, jsone_encode:encode({{json_utf8, <<"abc", 192>>}}))
  40. end},
  41. %% Numbers: Integer
  42. {"zero", fun() -> ?assertEqual({ok, <<"0">>}, jsone_encode:encode(0)) end},
  43. {"positive integer", fun() -> ?assertEqual({ok, <<"1">>}, jsone_encode:encode(1)) end},
  44. {"negative integer", fun() -> ?assertEqual({ok, <<"-1">>}, jsone_encode:encode(-1)) end},
  45. {"large number",
  46. fun() ->
  47. ?assertEqual({ok, <<"11111111111111111111111111111111111111111111111111111111111111111111111">>},
  48. jsone_encode:encode(11111111111111111111111111111111111111111111111111111111111111111111111))
  49. end},
  50. %% Numbers: Float",
  51. {"float",
  52. fun() ->
  53. Input = 1.234,
  54. ?assertMatch({ok, _}, jsone_encode:encode(Input)),
  55. ?assertEqual(Input, binary_to_float(element(2, jsone_encode:encode(Input))))
  56. end},
  57. {"float_format option",
  58. fun() ->
  59. Input = 1.23,
  60. ?assertEqual({ok, <<"1.22999999999999998224e+00">>}, jsone_encode:encode(Input)),
  61. ?assertEqual({ok, <<"1.2300e+00">>}, jsone_encode:encode(Input, [{float_format, [{scientific, 4}]}])),
  62. ?assertEqual({ok, <<"1.2e+00">>}, jsone_encode:encode(Input, [{float_format, [{scientific, 1}]}])),
  63. ?assertEqual({ok, <<"1.2300">>}, jsone_encode:encode(Input, [{float_format, [{decimals, 4}]}])),
  64. ?assertEqual({ok, <<"1.23">>}, jsone_encode:encode(Input, [{float_format, [{decimals, 4}, compact]}]))
  65. end},
  66. %% Strings
  67. {"simple string", fun() -> ?assertEqual({ok, <<"\"abc\"">>}, jsone_encode:encode(<<"abc">>)) end},
  68. {"atom is regarded as string", fun() -> ?assertEqual({ok, <<"\"abc\"">>}, jsone_encode:encode(abc)) end},
  69. {"string: contains escaped characters",
  70. fun() ->
  71. Input = <<"\"\/\\\b\f\n\r\t">>,
  72. Expected = list_to_binary([$", [ [$\\, C] || C <- [$", $/, $\\, $b, $f, $n, $r, $t] ], $"]),
  73. ?assertEqual({ok, Expected}, jsone_encode:encode(Input)),
  74. ?assertEqual({ok, Expected}, jsone_encode:encode(Input, [native_utf8]))
  75. end},
  76. {"string: contains forward slashes",
  77. fun() ->
  78. Input = <<"1/2">>,
  79. ?assertEqual({ok, <<"\"1\\/2\"">>}, jsone_encode:encode(Input)),
  80. ?assertEqual({ok, <<"\"1/2\"">>}, jsone_encode:encode(Input, [native_forward_slash]))
  81. end},
  82. {"string: contains control characters",
  83. fun() ->
  84. Ctrls = lists:seq(16#00, 16#1F) -- [$\b, $\f, $\n, $\r, $\t],
  85. Input = list_to_binary(Ctrls),
  86. Expected = list_to_binary([$", [ io_lib:format("\\u00~2.16.0b", [C]) || C <- Ctrls ], $"]),
  87. ?assertEqual({ok, Expected}, jsone_encode:encode(Input)),
  88. ?assertEqual({ok, Expected}, jsone_encode:encode(Input, [native_utf8]))
  89. end},
  90. {"string: contains multi-byte (UTF-8 encoded) characters",
  91. fun() ->
  92. %% japanese
  93. Input1 = <<"あいうえお">>, % assumed that the encoding of this file is UTF-8
  94. Expected1 = <<"\"\\u3042\\u3044\\u3046\\u3048\\u304a\"">>,
  95. ?assertEqual({ok, Expected1}, jsone_encode:encode(Input1)),
  96. Expected12 = <<$", Input1/binary, $">>,
  97. ?assertEqual({ok, Expected12}, jsone_encode:encode(Input1, [native_utf8])),
  98. %% other multi-byte characters
  99. Input2 = <<"۝۞ႮႯ">>,
  100. Expected2 = <<"\"\\u06dd\\u06de\\u10ae\\u10af\"">>,
  101. ?assertEqual({ok, Expected2}, jsone_encode:encode(Input2)),
  102. Expected22 = <<$", Input2/binary, $">>,
  103. ?assertEqual({ok, Expected22}, jsone_encode:encode(Input2, [native_utf8]))
  104. end},
  105. {"string: contains surrogate pairs",
  106. fun() ->
  107. Input = <<"𢁉𢂚𢃼">>,
  108. Expected = <<"\"\\ud848\\udc49\\ud848\\udc9a\\ud848\\udcfc\"">>,
  109. ?assertEqual({ok, Expected}, jsone_encode:encode(Input))
  110. end},
  111. %% Strings variant: Datetimes
  112. {"datetime: iso8601: utc",
  113. fun() ->
  114. ?assertEqual({ok, <<"\"2015-06-25T14:57:25Z\"">>}, jsone_encode:encode({{2015, 6, 25}, {14, 57, 25}})),
  115. ?assertEqual({ok, <<"\"2015-06-25T14:57:25Z\"">>},
  116. jsone_encode:encode({{2015, 6, 25}, {14, 57, 25}}, [{datetime_format, iso8601}])),
  117. ?assertEqual({ok, <<"\"2015-06-25T14:57:25Z\"">>},
  118. jsone_encode:encode({{2015, 6, 25}, {14, 57, 25}}, [{datetime_format, {iso8601, utc}}]))
  119. end},
  120. {"datetime: iso8601: local",
  121. fun() ->
  122. {ok, Json} = jsone_encode:encode({{2015, 6, 25}, {14, 57, 25}}, [{datetime_format, {iso8601, local}}]),
  123. UTC = {{1970, 1, 2}, {0, 0, 0}},
  124. Local = calendar:universal_time_to_local_time({{1970, 1, 2}, {0, 0, 0}}),
  125. case UTC =:= Local of
  126. false ->
  127. ?assertMatch(<<"\"2015-06-25T14:57:25", _:6/binary, "\"">>, Json);
  128. true ->
  129. ?assertMatch(<<"\"2015-06-25T14:57:25Z\"">>, Json)
  130. end
  131. end},
  132. {"datetime: iso8601: timezone",
  133. fun() ->
  134. ?assertEqual({ok, <<"\"2015-06-25T14:57:25Z\"">>},
  135. jsone_encode:encode({{2015, 6, 25}, {14, 57, 25}}, [{datetime_format, {iso8601, 0}}])),
  136. ?assertEqual({ok, <<"\"2015-06-25T14:57:25+00:01\"">>},
  137. jsone_encode:encode({{2015, 6, 25}, {14, 57, 25}}, [{datetime_format, {iso8601, 60}}])),
  138. ?assertEqual({ok, <<"\"2015-06-25T14:57:25-00:01\"">>},
  139. jsone_encode:encode({{2015, 6, 25}, {14, 57, 25}}, [{datetime_format, {iso8601, -60}}]))
  140. end},
  141. {"datetime as head of array",
  142. ?_assertEqual({ok, <<"[\"2015-06-25T14:57:25Z\"]">>}, jsone_encode:encode([{{2015, 6, 25}, {14, 57, 25}}]))},
  143. {"datetime: iso8601: with fractions of seconds",
  144. fun() ->
  145. ?assertEqual({ok, <<"\"2015-06-25T14:57:25.325Z\"">>},
  146. jsone_encode:encode({{2015, 6, 25}, {14, 57, 25.3245}})),
  147. ?assertEqual({ok, <<"\"2015-06-25T14:57:05.320Z\"">>},
  148. jsone_encode:encode({{2015, 6, 25}, {14, 57, 5.32}}))
  149. end},
  150. %% Arrays
  151. {"simple array",
  152. fun() ->
  153. Input = [1, 2, 3],
  154. Expected = <<"[1,2,3]">>,
  155. ?assertEqual({ok, Expected}, jsone_encode:encode(Input))
  156. end},
  157. {"empty array",
  158. fun() ->
  159. Input = [],
  160. Expected = <<"[]">>,
  161. ?assertEqual({ok, Expected}, jsone_encode:encode(Input))
  162. end},
  163. %% Objects
  164. {"simple object",
  165. fun() ->
  166. Input1 = {[{<<"key">>, <<"value">>}, {<<"1">>, 2}]},
  167. Input2 = [{<<"key">>, <<"value">>}, {<<"1">>, 2}],
  168. Expected = <<"{\"key\":\"value\",\"1\":2}">>,
  169. ?assertEqual({ok, Expected}, jsone_encode:encode(Input1)),
  170. ?assertEqual({ok, Expected}, jsone_encode:encode(Input2))
  171. end},
  172. {"empty object",
  173. fun() ->
  174. Input1 = {[]},
  175. Input2 = [{}],
  176. Expected = <<"{}">>,
  177. ?assertEqual({ok, Expected}, jsone_encode:encode(Input1)),
  178. ?assertEqual({ok, Expected}, jsone_encode:encode(Input2))
  179. end},
  180. {"simple object: map",
  181. fun() ->
  182. Input = ?OBJ2(<<"1">>, 2, <<"key">>, <<"value">>),
  183. Expected = <<"{\"1\":2,\"key\":\"value\"}">>,
  184. ?assertEqual({ok, Expected}, jsone_encode:encode(Input))
  185. end},
  186. {"empty object: map",
  187. fun() ->
  188. Input = ?OBJ0,
  189. Expected = <<"{}">>,
  190. ?assertEqual({ok, Expected}, jsone_encode:encode(Input))
  191. end},
  192. {"atom key is allowed",
  193. fun() ->
  194. Expected = <<"{\"key\":2}">>,
  195. ?assertEqual({ok, Expected}, jsone_encode:encode({[{key, 2}]}))
  196. end},
  197. {"object_key_type option",
  198. fun() ->
  199. %% key: atom
  200. ?assertEqual({ok, <<"{\"a\":2}">>}, jsone_encode:encode(?OBJ1(a, 2), [{object_key_type, string}])), % OK
  201. ?assertEqual({ok, <<"{\"a\":2}">>}, jsone_encode:encode(?OBJ1(a, 2), [{object_key_type, scalar}])), % OK
  202. ?assertEqual({ok, <<"{\"a\":2}">>}, jsone_encode:encode(?OBJ1(a, 2), [{object_key_type, value}])), % OK
  203. %% key: number
  204. ?assertMatch({error, {badarg, _}}, jsone_encode:encode(?OBJ1(1, 2), [{object_key_type, string}])), % NG
  205. ?assertEqual({ok, <<"{\"1\":2}">>}, jsone_encode:encode(?OBJ1(1, 2), [{object_key_type, scalar}])), % OK
  206. ?assertEqual({ok, <<"{\"1\":2}">>}, jsone_encode:encode(?OBJ1(1, 2), [{object_key_type, value}])), % OK
  207. %% key: datetime
  208. ?assertMatch({error, {badarg, _}},
  209. jsone_encode:encode(?OBJ1({{2000, 1, 1}, {0, 0, 0}}, 2), [{object_key_type, string}])), % NG
  210. ?assertEqual({ok, <<"{\"2000-01-01T00:00:00Z\":2}">>},
  211. jsone_encode:encode(?OBJ1({{2000, 1, 1}, {0, 0, 0}}, 2), [{object_key_type, scalar}])), % OK
  212. ?assertEqual({ok, <<"{\"2000-01-01T00:00:00Z\":2}">>},
  213. jsone_encode:encode(?OBJ1({{2000, 1, 1}, {0, 0, 0}}, 2), [{object_key_type, value}])), % OK
  214. %% key: array
  215. ?assertMatch({error, {badarg, _}}, jsone_encode:encode(?OBJ1([1], 2), [{object_key_type, string}])), % NG
  216. ?assertMatch({error, {badarg, _}}, jsone_encode:encode(?OBJ1([1], 2), [{object_key_type, scalar}])), % NG
  217. ?assertEqual({ok, <<"{\"[1]\":2}">>}, jsone_encode:encode(?OBJ1([1], 2), [{object_key_type, value}])), % OK
  218. %% key: object
  219. ?assertMatch({error, {badarg, _}}, jsone_encode:encode(?OBJ1(?OBJ0, 2), [{object_key_type, string}])), % NG
  220. ?assertMatch({error, {badarg, _}}, jsone_encode:encode(?OBJ1(?OBJ0, 2), [{object_key_type, scalar}])), % NG
  221. ?assertEqual({ok, <<"{\"{}\":2}">>}, jsone_encode:encode(?OBJ1(?OBJ0, 2), [{object_key_type, value}])) % OK
  222. end},
  223. {"non binary object member key is disallowed",
  224. fun() ->
  225. ?assertMatch({error, {badarg, _}}, jsone_encode:encode({[{1, 2}]})),
  226. ?assertMatch({error, {badarg, _}}, jsone_encode:encode({[{"1", 2}]}))
  227. end},
  228. {"undefined_as_null option",
  229. fun() ->
  230. ?assertEqual({ok, <<"null">>}, jsone_encode:encode(undefined, [undefined_as_null])), % OK
  231. ?assertEqual({ok, <<"\"undefined\"">>}, jsone_encode:encode(undefined, [])) % OK
  232. end},
  233. {"skip_undefined option",
  234. fun() ->
  235. Object = #{
  236. <<"1">> => undefined,
  237. <<"2">> => 3,
  238. <<"3">> => undefined
  239. },
  240. ?assertEqual({ok, <<"{\"1\":null,\"2\":3,\"3\":null}">>},
  241. jsone_encode:encode(Object, [undefined_as_null])),
  242. ?assertEqual({ok, <<"{\"2\":3}">>}, jsone_encode:encode(Object, [skip_undefined]))
  243. end},
  244. %% Pretty Print
  245. {"space",
  246. fun() ->
  247. ?assertEqual({ok, <<"[]">>}, jsone_encode:encode([], [{space, 1}])),
  248. ?assertEqual({ok, <<"[1, 2, 3]">>}, jsone_encode:encode([1, 2, 3], [{space, 1}])),
  249. ?assertEqual({ok, <<"[1, 2, 3]">>}, jsone_encode:encode([1, 2, 3], [{space, 2}])),
  250. ?assertEqual({ok, <<"{}">>}, jsone_encode:encode(?OBJ0, [{space, 1}])),
  251. ?assertEqual({ok, <<"{\"a\": 1, \"b\": 2}">>}, jsone_encode:encode(?OBJ2(a, 1, b, 2), [{space, 1}])),
  252. ?assertEqual({ok, <<"{\"a\": 1, \"b\": 2}">>}, jsone_encode:encode(?OBJ2(a, 1, b, 2), [{space, 2}]))
  253. end},
  254. {"indent",
  255. fun() ->
  256. ?assertEqual({ok, <<"[]">>}, jsone_encode:encode([], [{indent, 1}])),
  257. ?assertEqual({ok, <<"[\n 1,\n 2,\n 3\n]">>}, jsone_encode:encode([1, 2, 3], [{indent, 1}])),
  258. ?assertEqual({ok, <<"[\n 1,\n 2,\n 3\n]">>}, jsone_encode:encode([1, 2, 3], [{indent, 2}])),
  259. ?assertEqual({ok, <<"{}">>}, jsone_encode:encode(?OBJ0, [{indent, 1}])),
  260. ?assertEqual({ok, <<"{\n \"a\":1,\n \"b\":2\n}">>},
  261. jsone_encode:encode(?OBJ2(a, 1, b, 2), [{indent, 1}])),
  262. ?assertEqual({ok, <<"{\n \"a\":1,\n \"b\":2\n}">>},
  263. jsone_encode:encode(?OBJ2(a, 1, b, 2), [{indent, 2}]))
  264. end},
  265. {"indent+space",
  266. fun() ->
  267. ?assertEqual({ok, <<"[]">>}, jsone_encode:encode([], [{indent, 1}, {space, 1}])),
  268. ?assertEqual({ok, <<"[\n 1,\n 2,\n 3\n]">>}, jsone_encode:encode([1, 2, 3], [{indent, 1}, {space, 1}])),
  269. ?assertEqual({ok, <<"[\n 1,\n 2,\n 3\n]">>},
  270. jsone_encode:encode([1, 2, 3], [{indent, 2}, {space, 2}])),
  271. ?assertEqual({ok, <<"{}">>}, jsone_encode:encode(?OBJ0, [{indent, 1}, {space, 1}])),
  272. ?assertEqual({ok, <<"{\n \"a\": 1,\n \"b\": 2\n}">>},
  273. jsone_encode:encode(?OBJ2(a, 1, b, 2), [{indent, 1}, {space, 1}])),
  274. ?assertEqual({ok, <<"{\n \"a\": 1,\n \"b\": 2\n}">>},
  275. jsone_encode:encode(?OBJ2(a, 1, b, 2), [{indent, 2}, {space, 2}]))
  276. end},
  277. %% `map_unknown_value` option
  278. {"`map_unknown_value` option",
  279. fun() ->
  280. Input = [{1, 2, 3, 4}],
  281. MapFun = fun({_, _, _, _} = Ip4) ->
  282. {ok, list_to_binary(inet:ntoa(Ip4))};
  283. (_) ->
  284. error
  285. end,
  286. Expected = <<"[\"1.2.3.4\"]">>,
  287. ?assertEqual(Expected, jsone:encode(Input, [{map_unknown_value, MapFun}]))
  288. end},
  289. {"`map_unknown_value` option with singleton tuple",
  290. fun() ->
  291. Input = [{foo}],
  292. MapFun = fun(Value) -> {ok, unicode:characters_to_binary(io_lib:format("~p~n", [Value]))} end,
  293. Expected = <<"[\"{foo}\\n\"]">>,
  294. ?assertEqual(Expected, jsone:encode(Input, [{map_unknown_value, MapFun}]))
  295. end},
  296. {"IP address",
  297. fun() ->
  298. Input = #{ip => {127, 0, 0, 1}},
  299. Expected = <<"{\"ip\":\"127.0.0.1\"}">>,
  300. ?assertEqual(Expected, jsone:encode(Input, [{map_unknown_value, fun jsone:ip_address_to_json_string/1}])),
  301. %% Without `map_unknown_value' option.
  302. ?assertMatch({error, _}, jsone:try_encode(Input, [{map_unknown_value, undefined}]))
  303. end},
  304. %% Others
  305. {"compound data",
  306. fun() ->
  307. Input = [true,
  308. {[{<<"1">>, 2}, {<<"array">>, [[[[1]]], {[{<<"ab">>, <<"cd">>}]}, [], ?OBJ0, false]}]},
  309. null],
  310. Expected = <<"[true,{\"1\":2,\"array\":[[[[1]]],{\"ab\":\"cd\"},[],{},false]},null]">>,
  311. ?assertEqual({ok, Expected}, jsone_encode:encode(Input)),
  312. PpExpected =
  313. <<"[\n true,\n {\n \"1\": 2,\n \"array\": [\n [\n [\n [\n 1\n ]\n ]\n ],\n {\n \"ab\": \"cd\"\n },\n [],\n {},\n false\n ]\n },\n null\n]">>,
  314. ?assertEqual({ok, PpExpected}, jsone_encode:encode(Input, [{indent, 1}, {space, 1}]))
  315. end},
  316. {"invalid value",
  317. fun() ->
  318. Pid = self(),
  319. PidString = list_to_binary(io_lib:format("~p", [Pid])),
  320. ?assertEqual({ok, <<$", PidString/binary, $">>}, jsone_encode:encode(Pid)),
  321. ?assertMatch({error, {badarg, _}}, jsone_encode:encode(Pid, [{map_unknown_value, undefined}]))
  322. end},
  323. {"wrong option", fun() -> ?assertError(badarg, jsone_encode:encode(1, [{no_such_option, hoge}])) end},
  324. {"canonical_form",
  325. fun() ->
  326. Obj1 = ?OBJECT_FROM_LIST([ {<<"key", (integer_to_binary(I))/binary>>, I} || I <- lists:seq(1000, 0, -1) ]),
  327. Obj2 = ?OBJECT_FROM_LIST([ {<<"key", (integer_to_binary(I))/binary>>, I} || I <- lists:seq(0, 1000, 1) ]),
  328. ?assertEqual(jsone_encode:encode(Obj1, [canonical_form]), jsone_encode:encode(Obj2, [canonical_form]))
  329. end}].