0
|
1 /*
|
|
2 json_parse_state.js
|
|
3 2012-06-01
|
|
4
|
|
5 Public Domain.
|
|
6
|
|
7 NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
|
8
|
|
9 This file creates a json_parse function.
|
|
10
|
|
11 json_parse(text, reviver)
|
|
12 This method parses a JSON text to produce an object or array.
|
|
13 It can throw a SyntaxError exception.
|
|
14
|
|
15 The optional reviver parameter is a function that can filter and
|
|
16 transform the results. It receives each of the keys and values,
|
|
17 and its return value is used instead of the original value.
|
|
18 If it returns what it received, then the structure is not modified.
|
|
19 If it returns undefined then the member is deleted.
|
|
20
|
|
21 Example:
|
|
22
|
|
23 // Parse the text. Values that look like ISO date strings will
|
|
24 // be converted to Date objects.
|
|
25
|
|
26 myData = json_parse(text, function (key, value) {
|
|
27 var a;
|
|
28 if (typeof value === 'string') {
|
|
29 a =
|
|
30 /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
|
|
31 if (a) {
|
|
32 return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
|
|
33 +a[5], +a[6]));
|
|
34 }
|
|
35 }
|
|
36 return value;
|
|
37 });
|
|
38
|
|
39 This is a reference implementation. You are free to copy, modify, or
|
|
40 redistribute.
|
|
41
|
|
42 This code should be minified before deployment.
|
|
43 See http://javascript.crockford.com/jsmin.html
|
|
44
|
|
45 USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
|
|
46 NOT CONTROL.
|
|
47 */
|
|
48
|
|
49 /*jslint regexp: false*/
|
|
50
|
|
51 /*members "", "\"", ",", "\/", ":", "[", "\\", "]", acomma, avalue, b,
|
|
52 call, colon, container, exec, f, false, firstavalue, firstokey,
|
|
53 fromCharCode, go, hasOwnProperty, key, length, n, null, ocomma, okey,
|
|
54 ovalue, pop, prototype, push, r, replace, slice, state, t, test, true,
|
|
55 value, "{", "}"
|
|
56 */
|
|
57
|
|
58 var json_parse = (function () {
|
|
59 "use strict";
|
|
60
|
|
61 // This function creates a JSON parse function that uses a state machine rather
|
|
62 // than the dangerous eval function to parse a JSON text.
|
|
63
|
|
64 var state, // The state of the parser, one of
|
|
65 // 'go' The starting state
|
|
66 // 'ok' The final, accepting state
|
|
67 // 'firstokey' Ready for the first key of the object or
|
|
68 // the closing of an empty object
|
|
69 // 'okey' Ready for the next key of the object
|
|
70 // 'colon' Ready for the colon
|
|
71 // 'ovalue' Ready for the value half of a key/value pair
|
|
72 // 'ocomma' Ready for a comma or closing }
|
|
73 // 'firstavalue' Ready for the first value of an array or
|
|
74 // an empty array
|
|
75 // 'avalue' Ready for the next value of an array
|
|
76 // 'acomma' Ready for a comma or closing ]
|
|
77 stack, // The stack, for controlling nesting.
|
|
78 container, // The current container object or array
|
|
79 key, // The current key
|
|
80 value, // The current value
|
|
81 escapes = { // Escapement translation table
|
|
82 '\\': '\\',
|
|
83 '"': '"',
|
|
84 '/': '/',
|
|
85 't': '\t',
|
|
86 'n': '\n',
|
|
87 'r': '\r',
|
|
88 'f': '\f',
|
|
89 'b': '\b'
|
|
90 },
|
|
91 string = { // The actions for string tokens
|
|
92 go: function () {
|
|
93 state = 'ok';
|
|
94 },
|
|
95 firstokey: function () {
|
|
96 key = value;
|
|
97 state = 'colon';
|
|
98 },
|
|
99 okey: function () {
|
|
100 key = value;
|
|
101 state = 'colon';
|
|
102 },
|
|
103 ovalue: function () {
|
|
104 state = 'ocomma';
|
|
105 },
|
|
106 firstavalue: function () {
|
|
107 state = 'acomma';
|
|
108 },
|
|
109 avalue: function () {
|
|
110 state = 'acomma';
|
|
111 }
|
|
112 },
|
|
113 number = { // The actions for number tokens
|
|
114 go: function () {
|
|
115 state = 'ok';
|
|
116 },
|
|
117 ovalue: function () {
|
|
118 state = 'ocomma';
|
|
119 },
|
|
120 firstavalue: function () {
|
|
121 state = 'acomma';
|
|
122 },
|
|
123 avalue: function () {
|
|
124 state = 'acomma';
|
|
125 }
|
|
126 },
|
|
127 action = {
|
|
128
|
|
129 // The action table describes the behavior of the machine. It contains an
|
|
130 // object for each token. Each object contains a method that is called when
|
|
131 // a token is matched in a state. An object will lack a method for illegal
|
|
132 // states.
|
|
133
|
|
134 '{': {
|
|
135 go: function () {
|
|
136 stack.push({state: 'ok'});
|
|
137 container = {};
|
|
138 state = 'firstokey';
|
|
139 },
|
|
140 ovalue: function () {
|
|
141 stack.push({container: container, state: 'ocomma', key: key});
|
|
142 container = {};
|
|
143 state = 'firstokey';
|
|
144 },
|
|
145 firstavalue: function () {
|
|
146 stack.push({container: container, state: 'acomma'});
|
|
147 container = {};
|
|
148 state = 'firstokey';
|
|
149 },
|
|
150 avalue: function () {
|
|
151 stack.push({container: container, state: 'acomma'});
|
|
152 container = {};
|
|
153 state = 'firstokey';
|
|
154 }
|
|
155 },
|
|
156 '}': {
|
|
157 firstokey: function () {
|
|
158 var pop = stack.pop();
|
|
159 value = container;
|
|
160 container = pop.container;
|
|
161 key = pop.key;
|
|
162 state = pop.state;
|
|
163 },
|
|
164 ocomma: function () {
|
|
165 var pop = stack.pop();
|
|
166 container[key] = value;
|
|
167 value = container;
|
|
168 container = pop.container;
|
|
169 key = pop.key;
|
|
170 state = pop.state;
|
|
171 }
|
|
172 },
|
|
173 '[': {
|
|
174 go: function () {
|
|
175 stack.push({state: 'ok'});
|
|
176 container = [];
|
|
177 state = 'firstavalue';
|
|
178 },
|
|
179 ovalue: function () {
|
|
180 stack.push({container: container, state: 'ocomma', key: key});
|
|
181 container = [];
|
|
182 state = 'firstavalue';
|
|
183 },
|
|
184 firstavalue: function () {
|
|
185 stack.push({container: container, state: 'acomma'});
|
|
186 container = [];
|
|
187 state = 'firstavalue';
|
|
188 },
|
|
189 avalue: function () {
|
|
190 stack.push({container: container, state: 'acomma'});
|
|
191 container = [];
|
|
192 state = 'firstavalue';
|
|
193 }
|
|
194 },
|
|
195 ']': {
|
|
196 firstavalue: function () {
|
|
197 var pop = stack.pop();
|
|
198 value = container;
|
|
199 container = pop.container;
|
|
200 key = pop.key;
|
|
201 state = pop.state;
|
|
202 },
|
|
203 acomma: function () {
|
|
204 var pop = stack.pop();
|
|
205 container.push(value);
|
|
206 value = container;
|
|
207 container = pop.container;
|
|
208 key = pop.key;
|
|
209 state = pop.state;
|
|
210 }
|
|
211 },
|
|
212 ':': {
|
|
213 colon: function () {
|
|
214 if (Object.hasOwnProperty.call(container, key)) {
|
|
215 throw new SyntaxError('Duplicate key "' + key + '"');
|
|
216 }
|
|
217 state = 'ovalue';
|
|
218 }
|
|
219 },
|
|
220 ',': {
|
|
221 ocomma: function () {
|
|
222 container[key] = value;
|
|
223 state = 'okey';
|
|
224 },
|
|
225 acomma: function () {
|
|
226 container.push(value);
|
|
227 state = 'avalue';
|
|
228 }
|
|
229 },
|
|
230 'true': {
|
|
231 go: function () {
|
|
232 value = true;
|
|
233 state = 'ok';
|
|
234 },
|
|
235 ovalue: function () {
|
|
236 value = true;
|
|
237 state = 'ocomma';
|
|
238 },
|
|
239 firstavalue: function () {
|
|
240 value = true;
|
|
241 state = 'acomma';
|
|
242 },
|
|
243 avalue: function () {
|
|
244 value = true;
|
|
245 state = 'acomma';
|
|
246 }
|
|
247 },
|
|
248 'false': {
|
|
249 go: function () {
|
|
250 value = false;
|
|
251 state = 'ok';
|
|
252 },
|
|
253 ovalue: function () {
|
|
254 value = false;
|
|
255 state = 'ocomma';
|
|
256 },
|
|
257 firstavalue: function () {
|
|
258 value = false;
|
|
259 state = 'acomma';
|
|
260 },
|
|
261 avalue: function () {
|
|
262 value = false;
|
|
263 state = 'acomma';
|
|
264 }
|
|
265 },
|
|
266 'null': {
|
|
267 go: function () {
|
|
268 value = null;
|
|
269 state = 'ok';
|
|
270 },
|
|
271 ovalue: function () {
|
|
272 value = null;
|
|
273 state = 'ocomma';
|
|
274 },
|
|
275 firstavalue: function () {
|
|
276 value = null;
|
|
277 state = 'acomma';
|
|
278 },
|
|
279 avalue: function () {
|
|
280 value = null;
|
|
281 state = 'acomma';
|
|
282 }
|
|
283 }
|
|
284 };
|
|
285
|
|
286 function debackslashify(text) {
|
|
287
|
|
288 // Remove and replace any backslash escapement.
|
|
289
|
|
290 return text.replace(/\\(?:u(.{4})|([^u]))/g, function (a, b, c) {
|
|
291 return b ? String.fromCharCode(parseInt(b, 16)) : escapes[c];
|
|
292 });
|
|
293 }
|
|
294
|
|
295 return function (source, reviver) {
|
|
296
|
|
297 // A regular expression is used to extract tokens from the JSON text.
|
|
298 // The extraction process is cautious.
|
|
299
|
|
300 var r, // The result of the exec method.
|
|
301 tx = /^[\x20\t\n\r]*(?:([,:\[\]{}]|true|false|null)|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)|"((?:[^\r\n\t\\\"]|\\(?:["\\\/trnfb]|u[0-9a-fA-F]{4}))*)")/;
|
|
302
|
|
303 // Set the starting state.
|
|
304
|
|
305 state = 'go';
|
|
306
|
|
307 // The stack records the container, key, and state for each object or array
|
|
308 // that contains another object or array while processing nested structures.
|
|
309
|
|
310 stack = [];
|
|
311
|
|
312 // If any error occurs, we will catch it and ultimately throw a syntax error.
|
|
313
|
|
314 try {
|
|
315
|
|
316 // For each token...
|
|
317
|
|
318 for (;;) {
|
|
319 r = tx.exec(source);
|
|
320 if (!r) {
|
|
321 break;
|
|
322 }
|
|
323
|
|
324 // r is the result array from matching the tokenizing regular expression.
|
|
325 // r[0] contains everything that matched, including any initial whitespace.
|
|
326 // r[1] contains any punctuation that was matched, or true, false, or null.
|
|
327 // r[2] contains a matched number, still in string form.
|
|
328 // r[3] contains a matched string, without quotes but with escapement.
|
|
329
|
|
330 if (r[1]) {
|
|
331
|
|
332 // Token: Execute the action for this state and token.
|
|
333
|
|
334 action[r[1]][state]();
|
|
335
|
|
336 } else if (r[2]) {
|
|
337
|
|
338 // Number token: Convert the number string into a number value and execute
|
|
339 // the action for this state and number.
|
|
340
|
|
341 value = +r[2];
|
|
342 number[state]();
|
|
343 } else {
|
|
344
|
|
345 // String token: Replace the escapement sequences and execute the action for
|
|
346 // this state and string.
|
|
347
|
|
348 value = debackslashify(r[3]);
|
|
349 string[state]();
|
|
350 }
|
|
351
|
|
352 // Remove the token from the string. The loop will continue as long as there
|
|
353 // are tokens. This is a slow process, but it allows the use of ^ matching,
|
|
354 // which assures that no illegal tokens slip through.
|
|
355
|
|
356 source = source.slice(r[0].length);
|
|
357 }
|
|
358
|
|
359 // If we find a state/token combination that is illegal, then the action will
|
|
360 // cause an error. We handle the error by simply changing the state.
|
|
361
|
|
362 } catch (e) {
|
|
363 state = e;
|
|
364 }
|
|
365
|
|
366 // The parsing is finished. If we are not in the final 'ok' state, or if the
|
|
367 // remaining source contains anything except whitespace, then we did not have
|
|
368 //a well-formed JSON text.
|
|
369
|
|
370 if (state !== 'ok' || /[^\x20\t\n\r]/.test(source)) {
|
|
371 throw state instanceof SyntaxError ? state : new SyntaxError('JSON');
|
|
372 }
|
|
373
|
|
374 // If there is a reviver function, we recursively walk the new structure,
|
|
375 // passing each name/value pair to the reviver function for possible
|
|
376 // transformation, starting with a temporary root object that holds the current
|
|
377 // value in an empty key. If there is not a reviver function, we simply return
|
|
378 // that value.
|
|
379
|
|
380 return typeof reviver === 'function' ? (function walk(holder, key) {
|
|
381 var k, v, value = holder[key];
|
|
382 if (value && typeof value === 'object') {
|
|
383 for (k in value) {
|
|
384 if (Object.prototype.hasOwnProperty.call(value, k)) {
|
|
385 v = walk(value, k);
|
|
386 if (v !== undefined) {
|
|
387 value[k] = v;
|
|
388 } else {
|
|
389 delete value[k];
|
|
390 }
|
|
391 }
|
|
392 }
|
|
393 }
|
|
394 return reviver.call(holder, key, value);
|
|
395 }({'': value}, '')) : value;
|
|
396 };
|
|
397 }());
|