Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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