Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 716 lines (620 sloc) 25.169 kb
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
1 %%% Copyright (c) 2005-2006, A2Z Development USA, Inc. All Rights Reserved.
2 %%%
3 %%% The contents of this file are subject to the Erlang Public License,
4 %%% Version 1.1, (the "License"); you may not use this file except in
5 %%% compliance with the License. You should have received a copy of the
6 %%% Erlang Public License along with this software. If not, it can be
7 %%% retrieved via the world wide web at http://www.erlang.org/.
8 %%%
9 %%% Software distributed under the License is distributed on an "AS IS"
10 %%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11 %%% the License for the specific language governing rights and limitations
12 %%% under the License.
13 %%%
14 %%% The Initial Developer of the Original Code is A2Z Development USA, Inc.
15 %%% All Rights Reserved.
16
17 -module(json2).
18 -export([encode/1, decode_string/1, decode/2]).
19 -export([is_obj/1, obj_new/0, obj_fetch/2, obj_find/2, obj_is_key/2]).
20 -export([obj_store/3, obj_from_list/1, obj_fold/3]).
21 -export([test/0]).
22 -author("Jim Larson <jalarson@amazon.com>, Robert Wai-Chi Chu <robchu@amazon.com>").
23 -author("Gaspar Chilingarov <nm@web.am>, Gurgen Tumanyan <barbarian@armkb.com>").
24 -author("Steve Vinoski <vinoski@ieee.org>").
25 -vsn("3").
26
27 %%% JavaScript Object Notation ("JSON", http://www.json.org) is a simple
28 %%% data syntax meant as a lightweight alternative to other representations,
29 %%% such as XML. JSON is natively supported by JavaScript, but many
30 %%% other languages have conversion libraries available.
31 %%%
32 %%% This module translates JSON types into the following Erlang types:
33 %%%
34 %%% JSON Erlang
35 %%% ---- ------
36 %%% number number
37 %%% string string
38 %%% array {array, ElementList}
39 %%% object tagged proplist with string keys (i.e. {struct, PropList} )
40 %%% true, false, null atoms 'true', 'false', and 'null'
41 %%%
42 %%% Character Sets: the external representation, and the internal
43 %%% representation of strings, are lists of UTF-8 code units.
44 %%%
45 %%% Numbers: Thanks to Erlang's bignums, JSON-encoded integers of any
46 %%% size can be parsed. Conversely, extremely large integers may
47 %%% be JSON-encoded. This may cause problems for interoperability
48 %%% with JSON parsers which can't handle arbitrary-sized integers.
49 %%% Erlang's floats are of fixed precision and limited range, so
50 %%% syntactically valid JSON floating-point numbers could silently
51 %%% lose precision or noisily cause an overflow. However, most
52 %%% other JSON libraries are likely to behave in the same way.
53 %%%
54 %%% Strings: If we represented JSON string data as Erlang binaries,
55 %%% we would have to choose a particular unicode format. Instead,
56 %%% we use lists of UTF-16 code units, which applications may then
57 %%% change to binaries in their application-preferred manner.
58 %%%
59 %%% Arrays: Because of the string decision above, and Erlang's
60 %%% lack of a distinguished string datatype, JSON arrays map
61 %%% to {array, ArrayElementList}, where ArrayElementList -> list.
62 %%%
63 %%% Objects: Though not explicitly stated in the JSON "spec",
64 %%% JSON's JavaScript heritage mandates that member names must
65 %%% be unique within an object. The object/tuple ambiguity is
66 %%% not a problem, since the atom 'struct' is not an
67 %%% allowable value. Object keys may be atoms or strings on
68 %%% encoding but are always decoded as strings.
69 %%%
70
71 %%% ENCODING
72
73 %% Encode an erlang number, string, tuple, or object to JSON syntax, as a
74 %% possibly deep list of UTF-8 code units, throwing a runtime error in the
75 %% case of un-convertible input.
76 %% Note: object keys may be either strings or atoms.
77
78 encode(true) -> "true";
79 encode(false) -> "false";
80 encode(null) -> "null";
81 encode(undefined) -> "null";
82 encode(B) when is_binary(B) -> encode_string(B);
83 encode(I) when is_integer(I) -> integer_to_list(I);
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
84 encode(F) when is_float(F) -> float_to_list(F);
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
85 encode(L) when is_list(L) ->
86 case is_string(L) of
87 yes -> encode_string(L);
88 unicode -> encode_string(xmerl_ucs:to_utf8(L));
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
89 no -> encode({array, L})
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
90 end;
91 encode({array, Props}) when is_list(Props) -> encode_array(Props);
92 encode({struct, Props} = T) when is_list(Props) -> encode_object(T);
93 encode(Bad) -> exit({json_encode, {bad_term, Bad}}).
94
95 %% Encode an Erlang string to JSON.
96 %% Accumulate strings in reverse.
97
98 encode_string(B) when is_binary(B) -> encode_string(binary_to_list(B));
99 encode_string(S) -> encode_string(S, [$"]).
100
101 encode_string([], Acc) -> lists:reverse([$" | Acc]);
102 encode_string([C | Cs], Acc) ->
103 case C of
104 $" -> encode_string(Cs, [$", $\\ | Acc]);
105 % (don't escape solidus on encode)
106 $\\ -> encode_string(Cs, [$\\, $\\ | Acc]);
107 $\b -> encode_string(Cs, [$b, $\\ | Acc]); % note missing \
108 $\f -> encode_string(Cs, [$f, $\\ | Acc]);
109 $\n -> encode_string(Cs, [$n, $\\ | Acc]);
110 $\r -> encode_string(Cs, [$r, $\\ | Acc]);
111 $\t -> encode_string(Cs, [$t, $\\ | Acc]);
112 C when C >= 0, C < $\s ->
113 % Control characters must be unicode-encoded.
114 Hex = lists:flatten(io_lib:format("~4.16.0b", [C])),
115 encode_string(Cs, lists:reverse(Hex) ++ "u\\" ++ Acc); % "
116 C when C =< 16#FFFF -> encode_string(Cs, [C | Acc]);
117 _ -> exit({json_encode, {bad_char, C}})
118 end.
119
120 %% Encode an Erlang object as a JSON object, allowing string or atom keys.
121 %% Note that order is irrelevant in both internal and external object
122 %% representations. Nevertheless, the output will respect the order
123 %% of the input.
124
125 encode_object({struct, _Props} = Obj) ->
126 M = obj_fold(fun({Key, Value}, Acc) ->
127 S = case Key of
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
128 B when is_binary(B) -> encode_string(B);
129 L when is_list(L) ->
130 case is_string(L) of
131 yes -> encode_string(L);
132 unicode -> encode_string(xmerl_ucs:to_utf8(L));
133 no -> exit({json_encode, {bad_key, Key}})
134 end;
135 A when is_atom(A) -> encode_string(atom_to_list(A));
136 _ -> exit({json_encode, {bad_key, Key}})
137 end,
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
138 V = encode(Value),
139 case Acc of
140 [] -> [S, $:, V];
141 _ -> [Acc, $,, S, $:, V]
142 end
143 end, [], Obj),
144 [${, M, $}].
145
146 %% Encode an Erlang tuple as a JSON array.
147 %% Order *is* significant in a JSON array!
148
149 encode_array(T) ->
150 M = lists:foldl(fun(E, Acc) ->
151 V = encode(E),
152 case Acc of
153 [] -> V;
154 _ -> [Acc, $,, V]
155 end
156 end, [], T),
157 [$[, M, $]].
158
159 %%% SCANNING
160 %%%
161 %%% Scanning funs return either:
162 %%% {done, Result, LeftOverChars}
163 %%% if a complete token is recognized, or
164 %%% {more, Continuation}
165 %%% if more input is needed.
166 %%% Result is {ok, Term}, 'eof', or {error, Reason}.
167 %%% Here, the Continuation is a simple Erlang string.
168 %%%
169 %%% Currently, error handling is rather crude - errors are recognized
170 %%% by match failures. EOF is handled only by number scanning, where
171 %%% it can delimit a number, and otherwise causes a match failure.
172 %%%
173 %%% Tokens are one of the following
174 %%% JSON string -> erlang string
175 %%% JSON number -> erlang number
176 %%% true, false, null -> erlang atoms
177 %%% { } [ ] : , -> lcbrace rcbrace lsbrace rsbrace colon comma
178
179 token([]) -> {more, []};
180 token(eof) -> {done, eof, []};
181
182 token("true" ++ Rest) -> {done, {ok, true}, Rest};
183 token("tru") -> {more, "tru"};
184 token("tr") -> {more, "tr"};
185 token("t") -> {more, "t"};
186
187 token("false" ++ Rest) -> {done, {ok, false}, Rest};
188 token("fals") -> {more, "fals"};
189 token("fal") -> {more, "fal"};
190 token("fa") -> {more, "fa"};
191 token("f") -> {more, "f"};
192
193 token("null" ++ Rest) -> {done, {ok, null}, Rest};
194 token("nul") -> {more, "nul"};
195 token("nu") -> {more, "nu"};
196 token("n") -> {more, "n"};
197
198 token([C | Cs] = Input) ->
199 case C of
200 $\s -> token(Cs); % eat whitespace
201 $\t -> token(Cs); % eat whitespace
202 $\n -> token(Cs); % eat whitespace
203 $\r -> token(Cs); % eat whitespace
204 $" -> scan_string(Input);
205 $- -> scan_number(Input);
206 D when D >= $0, D =< $9-> scan_number(Input);
207 ${ -> {done, {ok, lcbrace}, Cs};
208 $} -> {done, {ok, rcbrace}, Cs};
209 $[ -> {done, {ok, lsbrace}, Cs};
210 $] -> {done, {ok, rsbrace}, Cs};
211 $: -> {done, {ok, colon}, Cs};
212 $, -> {done, {ok, comma}, Cs};
213 _ -> {done, {error, {bad_char, C}}, Cs}
214 end.
215
216 scan_string([$" | Cs] = Input) ->
217 scan_string(Cs, [], Input).
218
219 %% Accumulate in reverse order, save original start-of-string for continuation.
220
221 scan_string([], _, X) -> {more, X};
222 scan_string(eof, _, X) -> {done, {error, missing_close_quote}, X};
223 scan_string([$" | Rest], A, _) -> {done, {ok, lists:reverse(A)}, Rest};
224 scan_string([$\\], _, X) -> {more, X};
225 scan_string([$\\, $u, U1, U2, U3, U4 | Rest], A, X) ->
226 scan_string(Rest, [uni_char([U1, U2, U3, U4]) | A], X);
227 scan_string([$\\, $u | _], _, X) -> {more, X};
228 scan_string([$\\, C | Rest], A, X) ->
229 scan_string(Rest, [esc_to_char(C) | A], X);
230 scan_string([C | Rest], A, X) ->
231 scan_string(Rest, [C | A], X).
232
233 %% Given a list of hex characters, convert to the corresponding integer.
234
235 uni_char(HexList) ->
236 erlang:list_to_integer(HexList, 16).
237
238 esc_to_char($") -> $";
239 esc_to_char($/) -> $/;
240 esc_to_char($\\) -> $\\;
241 esc_to_char($b) -> $\b;
242 esc_to_char($f) -> $\f;
243 esc_to_char($n) -> $\n;
244 esc_to_char($r) -> $\r;
245 esc_to_char($t) -> $\t.
246
247 scan_number([]) -> {more, []};
248 scan_number(eof) -> {done, {error, incomplete_number}, []};
249 scan_number([$-, $- | _Ds]) -> {done, {error, invalid_number}, []};
250 scan_number([$- | Ds] = Input) ->
251 case scan_number(Ds) of
252 {more, _Cont} -> {more, Input};
253 {done, {ok, N}, CharList} -> {done, {ok, -1 * N}, CharList};
254 {done, Other, Chars} -> {done, Other, Chars}
255 end;
256 scan_number([D | Ds] = Input) when D >= $0, D =< $9 ->
257 scan_number(Ds, D - $0, Input).
258
259 %% Numbers don't have a terminator, so stop at the first non-digit,
260 %% and ask for more if we run out.
261
262 scan_number([], _A, X) -> {more, X};
263 scan_number(eof, A, _X) -> {done, {ok, A}, eof};
264 scan_number([$.], _A, X) -> {more, X};
265 scan_number([$., D | Ds], A, X) when D >= $0, D =< $9 ->
266 scan_fraction([D | Ds], A, X);
267 scan_number([D | Ds], A, X) when A > 0, D >= $0, D =< $9 ->
268 % Note that nonzero numbers can't start with "0".
269 scan_number(Ds, 10 * A + (D - $0), X);
270 scan_number([D | Ds], A, X) when D == $E; D == $e ->
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
271 scan_exponent_begin(Ds, integer_to_list(A) ++ ".0", X);
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
272 scan_number([D | _] = Ds, A, _X) when D < $0; D > $9 ->
273 {done, {ok, A}, Ds}.
274
275 scan_fraction(Ds, I, X) -> scan_fraction(Ds, [], I, X).
276
277 scan_fraction([], _Fs, _I, X) -> {more, X};
278 scan_fraction(eof, Fs, I, _X) ->
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
279 R = list_to_float(lists:append([integer_to_list(I), ".", lists:reverse(Fs)])),
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
280 {done, {ok, R}, eof};
281 scan_fraction([D | Ds], Fs, I, X) when D >= $0, D =< $9 ->
282 scan_fraction(Ds, [D | Fs], I, X);
283 scan_fraction([D | Ds], Fs, I, X) when D == $E; D == $e ->
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
284 R = lists:append([integer_to_list(I), ".", lists:reverse(Fs)]),
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
285 scan_exponent_begin(Ds, R, X);
286 scan_fraction(Rest, Fs, I, _X) ->
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
287 R = list_to_float(lists:append([integer_to_list(I), ".", lists:reverse(Fs)])),
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
288 {done, {ok, R}, Rest}.
289
290 scan_exponent_begin(Ds, R, X) ->
291 scan_exponent_begin(Ds, [], R, X).
292
293 scan_exponent_begin([], _Es, _R, X) -> {more, X};
294 scan_exponent_begin(eof, _Es, _R, X) -> {done, {error, missing_exponent}, X};
295 scan_exponent_begin([D | Ds], Es, R, X) when D == $-;
296 D == $+;
297 D >= $0, D =< $9 ->
298 scan_exponent(Ds, [D | Es], R, X).
299
300 scan_exponent([], _Es, _R, X) -> {more, X};
301 scan_exponent(eof, Es, R, _X) ->
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
302 X = list_to_float(lists:append([R, "e", lists:reverse(Es)])),
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
303 {done, {ok, X}, eof};
304 scan_exponent([D | Ds], Es, R, X) when D >= $0, D =< $9 ->
305 scan_exponent(Ds, [D | Es], R, X);
306 scan_exponent(Rest, Es, R, _X) ->
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
307 X = list_to_float(lists:append([R, "e", lists:reverse(Es)])),
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
308 {done, {ok, X}, Rest}.
309
310 %%% PARSING
311 %%%
312 %%% The decode function takes a char list as input, but
313 %%% interprets the end of the list as only an end to the available
314 %%% input, and returns a "continuation" requesting more input.
315 %%% When additional characters are available, they, and the
316 %%% continuation, are fed into decode/2. You can use the atom 'eof'
317 %%% as a character to signal a true end to the input stream, and
318 %%% possibly flush out an unfinished number. The decode_string/1
319 %%% function appends 'eof' to its input and calls decode/1.
320 %%%
321 %%% Parsing and scanning errors are handled only by match failures.
322 %%% The external caller must take care to wrap the call in a "catch"
323 %%% or "try" if better error-handling is desired. Eventually parse
324 %%% or scan errors will be returned explicitly with a description,
325 %%% and someday with line numbers too.
326 %%%
327 %%% The parsing code uses a continuation-passing style to allow
328 %%% for the parsing to suspend at any point and be resumed when
329 %%% more input is available.
330 %%% See http://en.wikipedia.org/wiki/Continuation_passing_style
331
332 %% Return the first JSON value decoded from the input string.
333 %% The string must contain at least one complete JSON value.
334
335 decode_string(CharList) ->
336 {done, V, _} = decode([], CharList ++ eof),
337 V.
338
339 %% Attempt to decode a JSON value from the input string
340 %% and continuation, using empty list for the initial continuation.
341 %% Return {done, Result, LeftoverChars} if a value is recognized,
342 %% or {more, Continuation} if more input characters are needed.
343 %% The Result can be {ok, Value}, eof, or {error, Reason}.
344 %% The Continuation is then fed as an argument to decode/2 when
345 %% more input is available.
346 %% Use the atom 'eof' instead of a char list to signal
347 %% a true end to the input, and may flush a final number.
348
349 decode([], CharList) ->
350 decode(first_continuation(), CharList);
351
352 decode(Continuation, CharList) ->
353 {OldChars, Kt} = Continuation,
354 get_token(OldChars ++ CharList, Kt).
355
356 first_continuation() ->
357 {[], fun
358 (eof, Cs) ->
359 {done, eof, Cs};
360 (T, Cs) ->
361 parse_value(T, Cs, fun(V, C2) ->
362 {done, {ok, V}, C2}
363 end)
364 end}.
365
366 %% Continuation Kt must accept (TokenOrEof, Chars)
367
368 get_token(Chars, Kt) ->
369 case token(Chars) of
370 {done, {ok, T}, Rest} -> Kt(T, Rest);
371 {done, eof, Rest} -> Kt(eof, Rest);
372 {done, {error, Reason}, Rest} -> {done, {error, Reason}, Rest};
373 {more, X} -> {more, {X, Kt}}
374 end.
375
376 %% Continuation Kv must accept (Value, Chars)
377
378 parse_value(eof, C, _Kv) -> {done, {error, premature_eof}, C};
379 parse_value(true, C, Kv) -> Kv(true, C);
380 parse_value(false, C, Kv) -> Kv(false, C);
381 parse_value(null, C, Kv) -> Kv(null, C);
382 parse_value(S, C, Kv) when is_list(S) -> Kv(S, C);
383 parse_value(N, C, Kv) when is_number(N) -> Kv(N, C);
384 parse_value(lcbrace, C, Kv) -> parse_object(C, Kv);
385 parse_value(lsbrace, C, Kv) -> parse_array(C, Kv);
386 parse_value(_, C, _Kv) -> {done, {error, syntax_error}, C}.
387
388 %% Continuation Kv must accept (Value, Chars)
389
390 parse_object(Chars, Kv) ->
391 get_token(Chars, fun(T, C2) ->
392 Obj = obj_new(),
393 case T of
394 rcbrace -> Kv(Obj, C2); % empty object
395 _ -> parse_object(Obj, T, C2, Kv) % token must be string
396 end
397 end).
398
399 parse_object(_Obj, eof, C, _Kv) ->
400 {done, {error, premature_eof}, C};
401
402 parse_object(Obj, S, C, Kv) when is_list(S) -> % S is member name
403 get_token(C, fun
404 (colon, C2) ->
405 parse_object2(Obj, S, C2, Kv);
406 (T, C2) ->
407 {done, {error, {expecting_colon, T}}, C2}
408 end);
409
410 parse_object(_Obj, M, C, _Kv) ->
411 {done, {error, {member_name_not_string, M}}, C}.
412
413 parse_object2(Obj, S, C, Kv) ->
414 get_token(C, fun
415 (eof, C2) ->
416 {done, {error, premature_eof}, C2};
417 (T, C2) ->
418 parse_value(T, C2, fun(V, C3) -> % V is member value
419 Obj2 = obj_store(S, V, Obj),
420 get_token(C3, fun
421 (rcbrace, C4) -> % "}" end of object
422 {struct, PropList1} = Obj2,
423 Kv({struct, lists:reverse(PropList1)}, C4);
424 (comma, C4) -> % "," another member follows
425 get_token(C4, fun(T3, C5) ->
426 parse_object(Obj2, T3, C5, Kv)
427 end);
428 (eof, C4) ->
429 {done, {error, premature_eof}, C4};
430 (T2, C4) ->
431 {done, {error, {expecting_comma_or_curly, T2}}, C4}
432 end)
433 end)
434 end).
435
436 %% Continuation Kv must accept (Value, Chars)
437
438 parse_array(C, Kv) ->
439 get_token(C, fun
440 (eof, C2) -> {done, {error, premature_eof}, C2};
441 (rsbrace, C2) -> Kv({array, []}, C2); % empty array
442 (T, C2) -> parse_array([], T, C2, Kv)
443 end).
444
445 parse_array(E, T, C, Kv) ->
446 parse_value(T, C, fun(V, C2) ->
447 E2 = [V | E],
448 get_token(C2, fun
449 (rsbrace, C3) -> % "]" end of array
450 Kv({array, lists:reverse(E2)}, C3);
451
452 (comma, C3) -> % "," another value follows
453 get_token(C3, fun(T3, C4) ->
454 parse_array(E2, T3, C4, Kv)
455 end);
456 (eof, C3) ->
457 {done, {error, premature_eof}, C3};
458 (T2, C3) ->
459 {done, {error, {expecting_comma_or_close_array, T2}}, C3}
460 end)
461 end).
462
463 %%% OBJECTS
464 %%%
465 %%% We'll use tagged property lists as the internal representation
466 %%% of JSON objects. Unordered lists perform worse than trees for
467 %%% lookup and modification of members, but we expect objects to be
468 %%% have only a few members. Lists also print better.
469
470 %% Is this a proper JSON object representation?
471
472 is_obj({struct, Props}) when is_list(Props) ->
473 lists:all(fun
474 ({Member, _Value}) when is_atom(Member); is_list(Member) -> true;
475 (_) -> false
476 end, Props);
477
478 is_obj(_) ->
479 false.
480
481 %% Create a new, empty object.
482
483 obj_new() ->
484 {struct, []}.
485
486 %% Fetch an object member's value, expecting it to be in the object.
487 %% Return value, runtime error if no member found with that name.
488
489 obj_fetch(Key, {struct, Props}) when is_list(Props) ->
490 case proplists:get_value(Key, Props) of
491 undefined ->
492 exit({struct_no_key, Key});
493 Value ->
494 Value
495 end.
496
497 %% Fetch an object member's value, or indicate that there is no such member.
498 %% Return {ok, Value} or 'error'.
499
500 obj_find(Key, {struct, Props}) when is_list(Props) ->
501 case proplists:get_value(Key, Props) of
502 undefined ->
503 error;
504 Value ->
505 {ok, Value}
506 end.
507
508 obj_is_key(Key, {struct, Props}) ->
509 proplists:is_defined(Key, Props).
510
511 %% Store a new member in an object. Returns a new object.
512
513 obj_store(Key, Value, {struct, Props}) when is_list(Props) ->
514 {struct, [{Key, Value} | proplists:delete(Key, Props)]}.
515
516 %% Create an object from a list of Key/Value pairs.
517
518 obj_from_list(Props) ->
519 Obj = {struct, Props},
520 case is_obj(Obj) of
521 true -> Obj;
522 false -> exit(json_bad_object)
523 end.
524
525 %% Fold Fun across object, with initial accumulator Acc.
526 %% Fun should take (Value, Acc) as arguments and return Acc.
527
528 obj_fold(Fun, Acc, {struct, Props}) ->
529 lists:foldl(Fun, Acc, Props).
530
531 is_string([]) -> yes;
532 is_string(List) -> is_string(List, non_unicode).
533
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
534 is_string([C|Rest], non_unicode) when is_integer(C), C >= 0, C =< 255 ->
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
535 is_string(Rest, non_unicode);
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
536 is_string([C|Rest], _) when is_integer(C), C =< 65000 -> is_string(Rest, unicode);
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
537 is_string([], non_unicode) -> yes;
538 is_string([], unicode) -> unicode;
539 is_string(_, _) -> no.
540
541
542 %%% TESTING
543 %%%
544 %%% We can't expect to round-trip from JSON -> Erlang -> JSON,
545 %%% due to the degrees of freedom in the JSON syntax: whitespace,
546 %%% and ordering of object members. We can, however, expect to
547 %%% round-trip from Erlang -> JSON -> Erlang, so the JSON parsing
548 %%% tests will in fact test the Erlang equivalence of the
549 %%% JSON -> Erlang -> JSON -> Erlang coding chain.
550
551 %% Test driver. Return 'ok' or {failed, Failures}.
552
553 test() ->
554 E2Js = e2j_test_vec(),
555 Failures =
556 lists:foldl(
557 fun({E, J}, Fs) ->
558 case (catch test_e2j(E, J)) of
559 ok ->
560 case (catch round_trip(E)) of
561 ok ->
562 case (catch round_trip_one_char(E)) of
563 ok ->
564 Fs;
565 Reason ->
566 [{round_trip_one_char, E, Reason} | Fs]
567 end;
568 Reason ->
569 [{round_trip, E, Reason} | Fs]
570 end;
571 Reason ->
572 [{erlang_to_json, E, J, Reason} | Fs]
573 end;
574 (end_of_tests, Fs) ->
575 Fs
576 end, [], E2Js),
577 case Failures of
578 [] -> ok;
579 _ -> {failed, Failures}
580 end.
581
582 %% Test for conversion from Erlang to JSON. Note that unequal strings
583 %% may represent equal JSON data, due to discretionary whitespace,
584 %% object member order, trailing zeroes in floating point, etc.
585 %% Legitimate changes to the encoding routines may require tweaks to
586 %% the reference JSON strings in e2j_test_vec().
587
661aaf9 @vinoski fixes for json2.erl and json.erl (Nico Kruber)
vinoski authored
588 test_e2j(E, _) when is_float(E) ->
589 J2 = lists:flatten(encode(E)),
590 E2 = list_to_float(J2),
591 Rel = abs(E2 - E)/E,
592 true = Rel < 0.005,
593 ok;
96534b2 @vinoski replace json.erl with json2.erl to fix issue 50
vinoski authored
594 test_e2j(E, J) ->
595 J2 = lists:flatten(encode(E)),
596 J = J2, % raises error if unequal
597 ok.
598
599 %% Test that Erlang -> JSON -> Erlang round-trip yields equivalent term.
600
601 round_trip(E) ->
602 J2 = lists:flatten(encode(E)),
603 {ok, E2} = decode_string(J2),
604 true = equiv(E, E2), % raises error if false
605 ok.
606
607 %% Round-trip with one character at a time to test all continuations.
608
609 round_trip_one_char(E) ->
610 J = lists:flatten(encode(E)),
611 {done, {ok, E2}, _} = lists:foldl(fun(C, Ret) ->
612 case Ret of
613 {done, _, _} -> Ret;
614 {more, Cont} -> decode(Cont, [C])
615 end
616 end, {more, first_continuation()}, J ++ [eof]),
617 true = equiv(E, E2), % raises error if false
618 ok.
619
620 %% Test for equivalence of Erlang terms.
621 %% Due to arbitrary order of construction, equivalent objects might
622 %% compare unequal as erlang terms, so we need to carefully recurse
623 %% through aggregates (arrays and objects).
624
625 equiv({struct, Props1}, {struct, Props2}) ->
626 equiv_object(Props1, Props2);
627 equiv({array, ArrayList1}, {array, ArrayList2}) ->
628 equiv_array(ArrayList1, ArrayList2);
629 equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2;
630 equiv(S1, S2) when is_list(S1), is_list(S2) ->
631 case {is_string(S1), is_string(S2)} of
632 {unicode, unicode} ->
633 xmerl_ucs:to_utf8(S1) == xmerl_ucs:to_utf8(S2);
634 {unicode, _} ->
635 xmerl_ucs:to_utf8(S1) == S2;
636 {_, unicode} ->
637 S1 == xmerl_ucs:to_utf8(S2);
638 _ ->
639 S1 == S2
640 end;
641 equiv(true, true) -> true;
642 equiv(false, false) -> true;
643 equiv(null, null) -> true.
644
645 %% Object representation and traversal order is unknown.
646 %% Use the sledgehammer and sort property lists.
647
648 equiv_object(Props1, Props2) ->
649 L1 = lists:keysort(1, Props1),
650 L2 = lists:keysort(1, Props2),
651 Pairs = lists:zip(L1, L2),
652 true = lists:all(fun({{K1, V1}, {K2, V2}}) ->
653 equiv(K1, K2) and equiv(V1, V2)
654 end, Pairs).
655
656 %% Recursively compare array elements for equivalence.
657
658 equiv_array([], []) ->
659 true;
660 equiv_array(A1, A2) when length(A1) == length(A2) ->
661 lists:all(fun({E1,E2}) ->
662 equiv(E1, E2)
663 end, lists:zip(A1, A2)).
664
665 e2j_test_vec() -> [
666 {1, "1"},
667 {3.1416, "3.14160"}, % text representation may truncate, trail zeroes
668 {-1, "-1"},
669 {-3.1416, "-3.14160"},
670 {12.0e10, "1.20000e+11"},
671 {1.234E+10, "1.23400e+10"},
672 {-1.234E-10, "-1.23400e-10"},
673 {"foo", "\"foo\""},
674 {"foo" ++ [500] ++ "bar", [$", $f, $o, $o, $\307, $\264, $b, $a, $r, $"]},
675 {"foo" ++ [5] ++ "bar", "\"foo\\u0005bar\""},
676 {"", "\"\""},
677 {[], "\"\""},
678 {"\n\n\n", "\"\\n\\n\\n\""},
679 {obj_new(), "{}"},
680 {obj_from_list([{"foo", "bar"}]), "{\"foo\":\"bar\"}"},
681 {obj_from_list([{"foo", "bar"}, {"baz", 123}]),
682 "{\"foo\":\"bar\",\"baz\":123}"},
683 {{array, []}, "[]"},
684 {{array, [{array, []}]}, "[[]]"},
685 {{array, [1, "foo"]}, "[1,\"foo\"]"},
686
687 % json array in a json object
688 {obj_from_list([{"foo", {array, [123]}}]),
689 "{\"foo\":[123]}"},
690
691 % json object in a json object
692 {obj_from_list([{"foo", obj_from_list([{"bar", true}])}]),
693 "{\"foo\":{\"bar\":true}}"},
694
695 % fold evaluation order
696 {obj_from_list([{"foo", {array, []}},
697 {"bar", obj_from_list([{"baz", true}])},
698 {"alice", "bob"}]),
699 "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"},
700
701 % json object in a json array
702 {{array, [-123, "foo", obj_from_list([{"bar", {array, []}}]), null]},
703 "[-123,\"foo\",{\"bar\":[]},null]"},
704
705 end_of_tests
706 ].
707
708 %%% TODO:
709 %%%
710 %%% Measure the overhead of the CPS-based parser by writing a conventional
711 %%% scanner-parser that expects all input to be available.
712 %%%
713 %%% Allow a compile-time option to decode object member names as atoms,
714 %%% to reduce the internal representation overheads when communicating
715 %%% with trusted peers.
Something went wrong with that request. Please try again.