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