Skip to content

HTTPS clone URL

Subversion checkout URL

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