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