Skip to content

HTTPS clone URL

Subversion checkout URL

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