PageRenderTime 104ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/mochijson.erl

http://github.com/basho/mochiweb
Erlang | 547 lines | 410 code | 57 blank | 80 comment | 12 complexity | 6f0d01db016ec21e5cc6f95fd89a763f MD5 | raw file
Possible License(s): MIT
  1. %% @author Bob Ippolito <bob@mochimedia.com>
  2. %% @copyright 2006 Mochi Media, Inc.
  3. %%
  4. %% Permission is hereby granted, free of charge, to any person obtaining a
  5. %% copy of this software and associated documentation files (the "Software"),
  6. %% to deal in the Software without restriction, including without limitation
  7. %% the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. %% and/or sell copies of the Software, and to permit persons to whom the
  9. %% Software is furnished to do so, subject to the following conditions:
  10. %%
  11. %% The above copyright notice and this permission notice shall be included in
  12. %% all copies or substantial portions of the Software.
  13. %%
  14. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  17. %% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. %% DEALINGS IN THE SOFTWARE.
  21. %% @doc Yet another JSON (RFC 4627) library for Erlang.
  22. -module(mochijson).
  23. -author('bob@mochimedia.com').
  24. -export([encoder/1, encode/1]).
  25. -export([decoder/1, decode/1]).
  26. -export([binary_encoder/1, binary_encode/1]).
  27. -export([binary_decoder/1, binary_decode/1]).
  28. % This is a macro to placate syntax highlighters..
  29. -define(Q, $\").
  30. -define(ADV_COL(S, N), S#decoder{column=N+S#decoder.column}).
  31. -define(INC_COL(S), S#decoder{column=1+S#decoder.column}).
  32. -define(INC_LINE(S), S#decoder{column=1, line=1+S#decoder.line}).
  33. %% @type json_string() = atom | string() | binary()
  34. %% @type json_number() = integer() | float()
  35. %% @type json_array() = {array, [json_term()]}
  36. %% @type json_object() = {struct, [{json_string(), json_term()}]}
  37. %% @type json_term() = json_string() | json_number() | json_array() |
  38. %% json_object()
  39. %% @type encoding() = utf8 | unicode
  40. %% @type encoder_option() = {input_encoding, encoding()} |
  41. %% {handler, function()}
  42. %% @type decoder_option() = {input_encoding, encoding()} |
  43. %% {object_hook, function()}
  44. %% @type bjson_string() = binary()
  45. %% @type bjson_number() = integer() | float()
  46. %% @type bjson_array() = [bjson_term()]
  47. %% @type bjson_object() = {struct, [{bjson_string(), bjson_term()}]}
  48. %% @type bjson_term() = bjson_string() | bjson_number() | bjson_array() |
  49. %% bjson_object()
  50. %% @type binary_encoder_option() = {handler, function()}
  51. %% @type binary_decoder_option() = {object_hook, function()}
  52. -record(encoder, {input_encoding=unicode,
  53. handler=null}).
  54. -record(decoder, {input_encoding=utf8,
  55. object_hook=null,
  56. line=1,
  57. column=1,
  58. state=null}).
  59. %% @spec encoder([encoder_option()]) -> function()
  60. %% @doc Create an encoder/1 with the given options.
  61. encoder(Options) ->
  62. State = parse_encoder_options(Options, #encoder{}),
  63. fun (O) -> json_encode(O, State) end.
  64. %% @spec encode(json_term()) -> iolist()
  65. %% @doc Encode the given as JSON to an iolist.
  66. encode(Any) ->
  67. json_encode(Any, #encoder{}).
  68. %% @spec decoder([decoder_option()]) -> function()
  69. %% @doc Create a decoder/1 with the given options.
  70. decoder(Options) ->
  71. State = parse_decoder_options(Options, #decoder{}),
  72. fun (O) -> json_decode(O, State) end.
  73. %% @spec decode(iolist()) -> json_term()
  74. %% @doc Decode the given iolist to Erlang terms.
  75. decode(S) ->
  76. json_decode(S, #decoder{}).
  77. %% @spec binary_decoder([binary_decoder_option()]) -> function()
  78. %% @doc Create a binary_decoder/1 with the given options.
  79. binary_decoder(Options) ->
  80. mochijson2:decoder(Options).
  81. %% @spec binary_encoder([binary_encoder_option()]) -> function()
  82. %% @doc Create a binary_encoder/1 with the given options.
  83. binary_encoder(Options) ->
  84. mochijson2:encoder(Options).
  85. %% @spec binary_encode(bjson_term()) -> iolist()
  86. %% @doc Encode the given as JSON to an iolist, using lists for arrays and
  87. %% binaries for strings.
  88. binary_encode(Any) ->
  89. mochijson2:encode(Any).
  90. %% @spec binary_decode(iolist()) -> bjson_term()
  91. %% @doc Decode the given iolist to Erlang terms, using lists for arrays and
  92. %% binaries for strings.
  93. binary_decode(S) ->
  94. mochijson2:decode(S).
  95. %% Internal API
  96. parse_encoder_options([], State) ->
  97. State;
  98. parse_encoder_options([{input_encoding, Encoding} | Rest], State) ->
  99. parse_encoder_options(Rest, State#encoder{input_encoding=Encoding});
  100. parse_encoder_options([{handler, Handler} | Rest], State) ->
  101. parse_encoder_options(Rest, State#encoder{handler=Handler}).
  102. parse_decoder_options([], State) ->
  103. State;
  104. parse_decoder_options([{input_encoding, Encoding} | Rest], State) ->
  105. parse_decoder_options(Rest, State#decoder{input_encoding=Encoding});
  106. parse_decoder_options([{object_hook, Hook} | Rest], State) ->
  107. parse_decoder_options(Rest, State#decoder{object_hook=Hook}).
  108. json_encode(true, _State) ->
  109. "true";
  110. json_encode(false, _State) ->
  111. "false";
  112. json_encode(null, _State) ->
  113. "null";
  114. json_encode(I, _State) when is_integer(I) ->
  115. integer_to_list(I);
  116. json_encode(F, _State) when is_float(F) ->
  117. mochinum:digits(F);
  118. json_encode(L, State) when is_list(L); is_binary(L); is_atom(L) ->
  119. json_encode_string(L, State);
  120. json_encode({array, Props}, State) when is_list(Props) ->
  121. json_encode_array(Props, State);
  122. json_encode({struct, Props}, State) when is_list(Props) ->
  123. json_encode_proplist(Props, State);
  124. json_encode(Bad, #encoder{handler=null}) ->
  125. exit({json_encode, {bad_term, Bad}});
  126. json_encode(Bad, State=#encoder{handler=Handler}) ->
  127. json_encode(Handler(Bad), State).
  128. json_encode_array([], _State) ->
  129. "[]";
  130. json_encode_array(L, State) ->
  131. F = fun (O, Acc) ->
  132. [$,, json_encode(O, State) | Acc]
  133. end,
  134. [$, | Acc1] = lists:foldl(F, "[", L),
  135. lists:reverse([$\] | Acc1]).
  136. json_encode_proplist([], _State) ->
  137. "{}";
  138. json_encode_proplist(Props, State) ->
  139. F = fun ({K, V}, Acc) ->
  140. KS = case K of
  141. K when is_atom(K) ->
  142. json_encode_string_utf8(atom_to_list(K));
  143. K when is_integer(K) ->
  144. json_encode_string(integer_to_list(K), State);
  145. K when is_list(K); is_binary(K) ->
  146. json_encode_string(K, State)
  147. end,
  148. VS = json_encode(V, State),
  149. [$,, VS, $:, KS | Acc]
  150. end,
  151. [$, | Acc1] = lists:foldl(F, "{", Props),
  152. lists:reverse([$\} | Acc1]).
  153. json_encode_string(A, _State) when is_atom(A) ->
  154. json_encode_string_unicode(xmerl_ucs:from_utf8(atom_to_list(A)));
  155. json_encode_string(B, _State) when is_binary(B) ->
  156. json_encode_string_unicode(xmerl_ucs:from_utf8(B));
  157. json_encode_string(S, #encoder{input_encoding=utf8}) ->
  158. json_encode_string_utf8(S);
  159. json_encode_string(S, #encoder{input_encoding=unicode}) ->
  160. json_encode_string_unicode(S).
  161. json_encode_string_utf8(S) ->
  162. [?Q | json_encode_string_utf8_1(S)].
  163. json_encode_string_utf8_1([C | Cs]) when C >= 0, C =< 16#7f ->
  164. NewC = case C of
  165. $\\ -> "\\\\";
  166. ?Q -> "\\\"";
  167. _ when C >= $\s, C < 16#7f -> C;
  168. $\t -> "\\t";
  169. $\n -> "\\n";
  170. $\r -> "\\r";
  171. $\f -> "\\f";
  172. $\b -> "\\b";
  173. _ when C >= 0, C =< 16#7f -> unihex(C);
  174. _ -> exit({json_encode, {bad_char, C}})
  175. end,
  176. [NewC | json_encode_string_utf8_1(Cs)];
  177. json_encode_string_utf8_1(All=[C | _]) when C >= 16#80, C =< 16#10FFFF ->
  178. [?Q | Rest] = json_encode_string_unicode(xmerl_ucs:from_utf8(All)),
  179. Rest;
  180. json_encode_string_utf8_1([]) ->
  181. "\"".
  182. json_encode_string_unicode(S) ->
  183. [?Q | json_encode_string_unicode_1(S)].
  184. json_encode_string_unicode_1([C | Cs]) ->
  185. NewC = case C of
  186. $\\ -> "\\\\";
  187. ?Q -> "\\\"";
  188. _ when C >= $\s, C < 16#7f -> C;
  189. $\t -> "\\t";
  190. $\n -> "\\n";
  191. $\r -> "\\r";
  192. $\f -> "\\f";
  193. $\b -> "\\b";
  194. _ when C >= 0, C =< 16#10FFFF -> unihex(C);
  195. _ -> exit({json_encode, {bad_char, C}})
  196. end,
  197. [NewC | json_encode_string_unicode_1(Cs)];
  198. json_encode_string_unicode_1([]) ->
  199. "\"".
  200. dehex(C) when C >= $0, C =< $9 ->
  201. C - $0;
  202. dehex(C) when C >= $a, C =< $f ->
  203. C - $a + 10;
  204. dehex(C) when C >= $A, C =< $F ->
  205. C - $A + 10.
  206. hexdigit(C) when C >= 0, C =< 9 ->
  207. C + $0;
  208. hexdigit(C) when C =< 15 ->
  209. C + $a - 10.
  210. unihex(C) when C < 16#10000 ->
  211. <<D3:4, D2:4, D1:4, D0:4>> = <<C:16>>,
  212. Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]],
  213. [$\\, $u | Digits];
  214. unihex(C) when C =< 16#10FFFF ->
  215. N = C - 16#10000,
  216. S1 = 16#d800 bor ((N bsr 10) band 16#3ff),
  217. S2 = 16#dc00 bor (N band 16#3ff),
  218. [unihex(S1), unihex(S2)].
  219. json_decode(B, S) when is_binary(B) ->
  220. json_decode(binary_to_list(B), S);
  221. json_decode(L, S) ->
  222. {Res, L1, S1} = decode1(L, S),
  223. {eof, [], _} = tokenize(L1, S1#decoder{state=trim}),
  224. Res.
  225. decode1(L, S=#decoder{state=null}) ->
  226. case tokenize(L, S#decoder{state=any}) of
  227. {{const, C}, L1, S1} ->
  228. {C, L1, S1};
  229. {start_array, L1, S1} ->
  230. decode_array(L1, S1#decoder{state=any}, []);
  231. {start_object, L1, S1} ->
  232. decode_object(L1, S1#decoder{state=key}, [])
  233. end.
  234. make_object(V, #decoder{object_hook=null}) ->
  235. V;
  236. make_object(V, #decoder{object_hook=Hook}) ->
  237. Hook(V).
  238. decode_object(L, S=#decoder{state=key}, Acc) ->
  239. case tokenize(L, S) of
  240. {end_object, Rest, S1} ->
  241. V = make_object({struct, lists:reverse(Acc)}, S1),
  242. {V, Rest, S1#decoder{state=null}};
  243. {{const, K}, Rest, S1} when is_list(K) ->
  244. {colon, L2, S2} = tokenize(Rest, S1),
  245. {V, L3, S3} = decode1(L2, S2#decoder{state=null}),
  246. decode_object(L3, S3#decoder{state=comma}, [{K, V} | Acc])
  247. end;
  248. decode_object(L, S=#decoder{state=comma}, Acc) ->
  249. case tokenize(L, S) of
  250. {end_object, Rest, S1} ->
  251. V = make_object({struct, lists:reverse(Acc)}, S1),
  252. {V, Rest, S1#decoder{state=null}};
  253. {comma, Rest, S1} ->
  254. decode_object(Rest, S1#decoder{state=key}, Acc)
  255. end.
  256. decode_array(L, S=#decoder{state=any}, Acc) ->
  257. case tokenize(L, S) of
  258. {end_array, Rest, S1} ->
  259. {{array, lists:reverse(Acc)}, Rest, S1#decoder{state=null}};
  260. {start_array, Rest, S1} ->
  261. {Array, Rest1, S2} = decode_array(Rest, S1#decoder{state=any}, []),
  262. decode_array(Rest1, S2#decoder{state=comma}, [Array | Acc]);
  263. {start_object, Rest, S1} ->
  264. {Array, Rest1, S2} = decode_object(Rest, S1#decoder{state=key}, []),
  265. decode_array(Rest1, S2#decoder{state=comma}, [Array | Acc]);
  266. {{const, Const}, Rest, S1} ->
  267. decode_array(Rest, S1#decoder{state=comma}, [Const | Acc])
  268. end;
  269. decode_array(L, S=#decoder{state=comma}, Acc) ->
  270. case tokenize(L, S) of
  271. {end_array, Rest, S1} ->
  272. {{array, lists:reverse(Acc)}, Rest, S1#decoder{state=null}};
  273. {comma, Rest, S1} ->
  274. decode_array(Rest, S1#decoder{state=any}, Acc)
  275. end.
  276. tokenize_string(IoList=[C | _], S=#decoder{input_encoding=utf8}, Acc)
  277. when is_list(C); is_binary(C); C >= 16#7f ->
  278. List = xmerl_ucs:from_utf8(iolist_to_binary(IoList)),
  279. tokenize_string(List, S#decoder{input_encoding=unicode}, Acc);
  280. tokenize_string("\"" ++ Rest, S, Acc) ->
  281. {lists:reverse(Acc), Rest, ?INC_COL(S)};
  282. tokenize_string("\\\"" ++ Rest, S, Acc) ->
  283. tokenize_string(Rest, ?ADV_COL(S, 2), [$\" | Acc]);
  284. tokenize_string("\\\\" ++ Rest, S, Acc) ->
  285. tokenize_string(Rest, ?ADV_COL(S, 2), [$\\ | Acc]);
  286. tokenize_string("\\/" ++ Rest, S, Acc) ->
  287. tokenize_string(Rest, ?ADV_COL(S, 2), [$/ | Acc]);
  288. tokenize_string("\\b" ++ Rest, S, Acc) ->
  289. tokenize_string(Rest, ?ADV_COL(S, 2), [$\b | Acc]);
  290. tokenize_string("\\f" ++ Rest, S, Acc) ->
  291. tokenize_string(Rest, ?ADV_COL(S, 2), [$\f | Acc]);
  292. tokenize_string("\\n" ++ Rest, S, Acc) ->
  293. tokenize_string(Rest, ?ADV_COL(S, 2), [$\n | Acc]);
  294. tokenize_string("\\r" ++ Rest, S, Acc) ->
  295. tokenize_string(Rest, ?ADV_COL(S, 2), [$\r | Acc]);
  296. tokenize_string("\\t" ++ Rest, S, Acc) ->
  297. tokenize_string(Rest, ?ADV_COL(S, 2), [$\t | Acc]);
  298. tokenize_string([$\\, $u, C3, C2, C1, C0 | Rest], S, Acc) ->
  299. % coalesce UTF-16 surrogate pair?
  300. C = dehex(C0) bor
  301. (dehex(C1) bsl 4) bor
  302. (dehex(C2) bsl 8) bor
  303. (dehex(C3) bsl 12),
  304. tokenize_string(Rest, ?ADV_COL(S, 6), [C | Acc]);
  305. tokenize_string([C | Rest], S, Acc) when C >= $\s; C < 16#10FFFF ->
  306. tokenize_string(Rest, ?ADV_COL(S, 1), [C | Acc]).
  307. tokenize_number(IoList=[C | _], Mode, S=#decoder{input_encoding=utf8}, Acc)
  308. when is_list(C); is_binary(C); C >= 16#7f ->
  309. List = xmerl_ucs:from_utf8(iolist_to_binary(IoList)),
  310. tokenize_number(List, Mode, S#decoder{input_encoding=unicode}, Acc);
  311. tokenize_number([$- | Rest], sign, S, []) ->
  312. tokenize_number(Rest, int, ?INC_COL(S), [$-]);
  313. tokenize_number(Rest, sign, S, []) ->
  314. tokenize_number(Rest, int, S, []);
  315. tokenize_number([$0 | Rest], int, S, Acc) ->
  316. tokenize_number(Rest, frac, ?INC_COL(S), [$0 | Acc]);
  317. tokenize_number([C | Rest], int, S, Acc) when C >= $1, C =< $9 ->
  318. tokenize_number(Rest, int1, ?INC_COL(S), [C | Acc]);
  319. tokenize_number([C | Rest], int1, S, Acc) when C >= $0, C =< $9 ->
  320. tokenize_number(Rest, int1, ?INC_COL(S), [C | Acc]);
  321. tokenize_number(Rest, int1, S, Acc) ->
  322. tokenize_number(Rest, frac, S, Acc);
  323. tokenize_number([$., C | Rest], frac, S, Acc) when C >= $0, C =< $9 ->
  324. tokenize_number(Rest, frac1, ?ADV_COL(S, 2), [C, $. | Acc]);
  325. tokenize_number([E | Rest], frac, S, Acc) when E == $e; E == $E ->
  326. tokenize_number(Rest, esign, ?INC_COL(S), [$e, $0, $. | Acc]);
  327. tokenize_number(Rest, frac, S, Acc) ->
  328. {{int, lists:reverse(Acc)}, Rest, S};
  329. tokenize_number([C | Rest], frac1, S, Acc) when C >= $0, C =< $9 ->
  330. tokenize_number(Rest, frac1, ?INC_COL(S), [C | Acc]);
  331. tokenize_number([E | Rest], frac1, S, Acc) when E == $e; E == $E ->
  332. tokenize_number(Rest, esign, ?INC_COL(S), [$e | Acc]);
  333. tokenize_number(Rest, frac1, S, Acc) ->
  334. {{float, lists:reverse(Acc)}, Rest, S};
  335. tokenize_number([C | Rest], esign, S, Acc) when C == $-; C == $+ ->
  336. tokenize_number(Rest, eint, ?INC_COL(S), [C | Acc]);
  337. tokenize_number(Rest, esign, S, Acc) ->
  338. tokenize_number(Rest, eint, S, Acc);
  339. tokenize_number([C | Rest], eint, S, Acc) when C >= $0, C =< $9 ->
  340. tokenize_number(Rest, eint1, ?INC_COL(S), [C | Acc]);
  341. tokenize_number([C | Rest], eint1, S, Acc) when C >= $0, C =< $9 ->
  342. tokenize_number(Rest, eint1, ?INC_COL(S), [C | Acc]);
  343. tokenize_number(Rest, eint1, S, Acc) ->
  344. {{float, lists:reverse(Acc)}, Rest, S}.
  345. tokenize([], S=#decoder{state=trim}) ->
  346. {eof, [], S};
  347. tokenize([L | Rest], S) when is_list(L) ->
  348. tokenize(L ++ Rest, S);
  349. tokenize([B | Rest], S) when is_binary(B) ->
  350. tokenize(xmerl_ucs:from_utf8(B) ++ Rest, S);
  351. tokenize("\r\n" ++ Rest, S) ->
  352. tokenize(Rest, ?INC_LINE(S));
  353. tokenize("\n" ++ Rest, S) ->
  354. tokenize(Rest, ?INC_LINE(S));
  355. tokenize([C | Rest], S) when C == $\s; C == $\t ->
  356. tokenize(Rest, ?INC_COL(S));
  357. tokenize("{" ++ Rest, S) ->
  358. {start_object, Rest, ?INC_COL(S)};
  359. tokenize("}" ++ Rest, S) ->
  360. {end_object, Rest, ?INC_COL(S)};
  361. tokenize("[" ++ Rest, S) ->
  362. {start_array, Rest, ?INC_COL(S)};
  363. tokenize("]" ++ Rest, S) ->
  364. {end_array, Rest, ?INC_COL(S)};
  365. tokenize("," ++ Rest, S) ->
  366. {comma, Rest, ?INC_COL(S)};
  367. tokenize(":" ++ Rest, S) ->
  368. {colon, Rest, ?INC_COL(S)};
  369. tokenize("null" ++ Rest, S) ->
  370. {{const, null}, Rest, ?ADV_COL(S, 4)};
  371. tokenize("true" ++ Rest, S) ->
  372. {{const, true}, Rest, ?ADV_COL(S, 4)};
  373. tokenize("false" ++ Rest, S) ->
  374. {{const, false}, Rest, ?ADV_COL(S, 5)};
  375. tokenize("\"" ++ Rest, S) ->
  376. {String, Rest1, S1} = tokenize_string(Rest, ?INC_COL(S), []),
  377. {{const, String}, Rest1, S1};
  378. tokenize(L=[C | _], S) when C >= $0, C =< $9; C == $- ->
  379. case tokenize_number(L, sign, S, []) of
  380. {{int, Int}, Rest, S1} ->
  381. {{const, list_to_integer(Int)}, Rest, S1};
  382. {{float, Float}, Rest, S1} ->
  383. {{const, list_to_float(Float)}, Rest, S1}
  384. end.
  385. %%
  386. %% Tests
  387. %%
  388. -ifdef(TEST).
  389. -include_lib("eunit/include/eunit.hrl").
  390. %% testing constructs borrowed from the Yaws JSON implementation.
  391. %% Create an object from a list of Key/Value pairs.
  392. obj_new() ->
  393. {struct, []}.
  394. is_obj({struct, Props}) ->
  395. F = fun ({K, _}) when is_list(K) ->
  396. true;
  397. (_) ->
  398. false
  399. end,
  400. lists:all(F, Props).
  401. obj_from_list(Props) ->
  402. Obj = {struct, Props},
  403. case is_obj(Obj) of
  404. true -> Obj;
  405. false -> exit(json_bad_object)
  406. end.
  407. %% Test for equivalence of Erlang terms.
  408. %% Due to arbitrary order of construction, equivalent objects might
  409. %% compare unequal as erlang terms, so we need to carefully recurse
  410. %% through aggregates (tuples and objects).
  411. equiv({struct, Props1}, {struct, Props2}) ->
  412. equiv_object(Props1, Props2);
  413. equiv({array, L1}, {array, L2}) ->
  414. equiv_list(L1, L2);
  415. equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2;
  416. equiv(S1, S2) when is_list(S1), is_list(S2) -> S1 == S2;
  417. equiv(true, true) -> true;
  418. equiv(false, false) -> true;
  419. equiv(null, null) -> true.
  420. %% Object representation and traversal order is unknown.
  421. %% Use the sledgehammer and sort property lists.
  422. equiv_object(Props1, Props2) ->
  423. L1 = lists:keysort(1, Props1),
  424. L2 = lists:keysort(1, Props2),
  425. Pairs = lists:zip(L1, L2),
  426. true = lists:all(fun({{K1, V1}, {K2, V2}}) ->
  427. equiv(K1, K2) and equiv(V1, V2)
  428. end, Pairs).
  429. %% Recursively compare tuple elements for equivalence.
  430. equiv_list([], []) ->
  431. true;
  432. equiv_list([V1 | L1], [V2 | L2]) ->
  433. equiv(V1, V2) andalso equiv_list(L1, L2).
  434. e2j_vec_test() ->
  435. test_one(e2j_test_vec(utf8), 1).
  436. issue33_test() ->
  437. %% http://code.google.com/p/mochiweb/issues/detail?id=33
  438. Js = {struct, [{"key", [194, 163]}]},
  439. Encoder = encoder([{input_encoding, utf8}]),
  440. "{\"key\":\"\\u00a3\"}" = lists:flatten(Encoder(Js)).
  441. test_one([], _N) ->
  442. %% io:format("~p tests passed~n", [N-1]),
  443. ok;
  444. test_one([{E, J} | Rest], N) ->
  445. %% io:format("[~p] ~p ~p~n", [N, E, J]),
  446. true = equiv(E, decode(J)),
  447. true = equiv(E, decode(encode(E))),
  448. test_one(Rest, 1+N).
  449. e2j_test_vec(utf8) ->
  450. [
  451. {1, "1"},
  452. {3.1416, "3.14160"}, % text representation may truncate, trail zeroes
  453. {-1, "-1"},
  454. {-3.1416, "-3.14160"},
  455. {12.0e10, "1.20000e+11"},
  456. {1.234E+10, "1.23400e+10"},
  457. {-1.234E-10, "-1.23400e-10"},
  458. {10.0, "1.0e+01"},
  459. {123.456, "1.23456E+2"},
  460. {10.0, "1e1"},
  461. {"foo", "\"foo\""},
  462. {"foo" ++ [5] ++ "bar", "\"foo\\u0005bar\""},
  463. {"", "\"\""},
  464. {"\"", "\"\\\"\""},
  465. {"\n\n\n", "\"\\n\\n\\n\""},
  466. {"\\", "\"\\\\\""},
  467. {"\" \b\f\r\n\t\"", "\"\\\" \\b\\f\\r\\n\\t\\\"\""},
  468. {obj_new(), "{}"},
  469. {obj_from_list([{"foo", "bar"}]), "{\"foo\":\"bar\"}"},
  470. {obj_from_list([{"foo", "bar"}, {"baz", 123}]),
  471. "{\"foo\":\"bar\",\"baz\":123}"},
  472. {{array, []}, "[]"},
  473. {{array, [{array, []}]}, "[[]]"},
  474. {{array, [1, "foo"]}, "[1,\"foo\"]"},
  475. % json array in a json object
  476. {obj_from_list([{"foo", {array, [123]}}]),
  477. "{\"foo\":[123]}"},
  478. % json object in a json object
  479. {obj_from_list([{"foo", obj_from_list([{"bar", true}])}]),
  480. "{\"foo\":{\"bar\":true}}"},
  481. % fold evaluation order
  482. {obj_from_list([{"foo", {array, []}},
  483. {"bar", obj_from_list([{"baz", true}])},
  484. {"alice", "bob"}]),
  485. "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"},
  486. % json object in a json array
  487. {{array, [-123, "foo", obj_from_list([{"bar", {array, []}}]), null]},
  488. "[-123,\"foo\",{\"bar\":[]},null]"}
  489. ].
  490. -endif.