Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(74)

Side by Side Diff: sdk/lib/json/json_base.dart

Issue 12318031: Move json, uri, utf and crypto libraries into the VM. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sdk/lib/json/json.dart ('k') | sdk/lib/json/json_sources.gypi » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 // JSON parsing and serialization.
6
7 /**
8 * Error thrown by JSON serialization if an object cannot be serialized.
9 *
10 * The [unsupportedObject] field holds that object that failed to be serialized.
11 *
12 * If an object isn't directly serializable, the serializer calls the 'toJson'
13 * method on the object. If that call fails, the error will be stored in the
14 * [cause] field. If the call returns an object that isn't directly
15 * serializable, the [cause] will be null.
16 */
17 class JsonUnsupportedObjectError implements Error {
18 /** The object that could not be serialized. */
19 final unsupportedObject;
20 /** The exception thrown by object's [:toJson:] method, if any. */
21 final cause;
22 JsonUnsupportedObjectError(this.unsupportedObject) : cause = null;
23 JsonUnsupportedObjectError.withCause(this.unsupportedObject, this.cause);
24
25 String toString() {
26 if (cause != null) {
27 return "Calling toJson method on object failed.";
28 } else {
29 return "Object toJson method returns non-serializable value.";
30 }
31 }
32 }
33
34
35 /**
36 * Parses [json] and build the corresponding parsed JSON value.
37 *
38 * Parsed JSON values are of the types [num], [String], [bool], [Null],
39 * [List]s of parsed JSON values or [Map]s from [String] to parsed
40 * JSON values.
41 *
42 * The optional [revivier] function, if provided, is called once for each
43 * object or list property parsed. The arguments are the property name
44 * ([String]) or list index ([int]), and the value is the parsed value.
45 * The return value of the revivier will be used as the value of that property
46 * instead the parsed value.
47 *
48 * Throws [FormatException] if the input is not valid JSON text.
49 */
50 parse(String json, [reviver(var key, var value)]) {
51 BuildJsonListener listener;
52 if (reviver == null) {
53 listener = new BuildJsonListener();
54 } else {
55 listener = new ReviverJsonListener(reviver);
56 }
57 new JsonParser(json, listener).parse();
58 return listener.result;
59 }
60
61 /**
62 * Serializes [object] into a JSON string.
63 *
64 * Directly serializable types are [num], [String], [bool], [Null], [List]
65 * and [Map].
66 * For [List], the elements must all be serializable.
67 * For [Map], the keys must be [String] and the values must be serializable.
68 * If a value is any other type is attempted serialized, a "toJson()" method
69 * is invoked on the object and the result, which must be a directly
70 * serializable type, is serialized instead of the original value.
71 * If the object does not support this method, throws, or returns a
72 * value that is not directly serializable, a [JsonUnsupportedObjectError]
73 * exception is thrown. If the call throws (including the case where there
74 * is no nullary "toJson" method, the error is caught and stored in the
75 * [JsonUnsupportedObjectError]'s [:cause:] field.
76 *Json
77 * Objects should not change during serialization.
78 * If an object is serialized more than once, [stringify] is allowed to cache
79 * the JSON text for it. I.e., if an object changes after it is first
80 * serialized, the new values may or may not be reflected in the result.
81 */
82 String stringify(Object object) {
83 return _JsonStringifier.stringify(object);
84 }
85
86 /**
87 * Serializes [object] into [output] stream.
88 *
89 * Performs the same operations as [stringify] but outputs the resulting
90 * string to an existing [StringBuffer] instead of creating a new [String].
91 *
92 * If serialization fails by throwing, some data might have been added to
93 * [output], but it won't contain valid JSON text.
94 */
95 void printOn(Object object, StringBuffer output) {
96 return _JsonStringifier.printOn(object, output);
97 }
98
99 //// Implementation ///////////////////////////////////////////////////////////
100
101 // Simple API for JSON parsing.
102
103 abstract class JsonListener {
104 void handleString(String value) {}
105 void handleNumber(num value) {}
106 void handleBool(bool value) {}
107 void handleNull() {}
108 void beginObject() {}
109 void propertyName() {}
110 void propertyValue() {}
111 void endObject() {}
112 void beginArray() {}
113 void arrayElement() {}
114 void endArray() {}
115 /** Called on failure to parse [source]. */
116 void fail(String source, int position, String message) {}
117 }
118
119 /**
120 * A [JsonListener] that builds data objects from the parser events.
121 *
122 * This is a simple stack-based object builder. It keeps the most recently
123 * seen value in a variable, and uses it depending on the following event.
124 */
125 class BuildJsonListener extends JsonListener {
126 /**
127 * Stack used to handle nested containers.
128 *
129 * The current container is pushed on the stack when a new one is
130 * started. If the container is a [Map], there is also a current [key]
131 * which is also stored on the stack.
132 */
133 List stack = [];
134 /** The current [Map] or [List] being built. */
135 var currentContainer;
136 /** The most recently read property key. */
137 String key;
138 /** The most recently read value. */
139 var value;
140
141 /** Pushes the currently active container (and key, if a [Map]). */
142 void pushContainer() {
143 if (currentContainer is Map) stack.add(key);
144 stack.add(currentContainer);
145 }
146
147 /** Pops the top container from the [stack], including a key if applicable. */
148 void popContainer() {
149 value = currentContainer;
150 currentContainer = stack.removeLast();
151 if (currentContainer is Map) key = stack.removeLast();
152 }
153
154 void handleString(String value) { this.value = value; }
155 void handleNumber(num value) { this.value = value; }
156 void handleBool(bool value) { this.value = value; }
157 void handleNull() { this.value = value; }
158
159 void beginObject() {
160 pushContainer();
161 currentContainer = {};
162 }
163
164 void propertyName() {
165 key = value;
166 value = null;
167 }
168
169 void propertyValue() {
170 Map map = currentContainer;
171 map[key] = value;
172 key = value = null;
173 }
174
175 void endObject() {
176 popContainer();
177 }
178
179 void beginArray() {
180 pushContainer();
181 currentContainer = [];
182 }
183
184 void arrayElement() {
185 List list = currentContainer;
186 currentContainer.add(value);
187 value = null;
188 }
189
190 void endArray() {
191 popContainer();
192 }
193
194 /** Read out the final result of parsing a JSON string. */
195 get result {
196 assert(currentContainer == null);
197 return value;
198 }
199 }
200
201 typedef _Reviver(var key, var value);
202
203 class ReviverJsonListener extends BuildJsonListener {
204 final _Reviver reviver;
205 ReviverJsonListener(reviver(key, value)) : this.reviver = reviver;
206
207 void arrayElement() {
208 List list = currentContainer;
209 value = reviver(list.length, value);
210 super.arrayElement();
211 }
212
213 void propertyValue() {
214 value = reviver(key, value);
215 super.propertyValue();
216 }
217
218 get result {
219 return reviver("", value);
220 }
221 }
222
223 class JsonParser {
224 // A simple non-recursive state-based parser for JSON.
225 //
226 // Literal values accepted in states ARRAY_EMPTY, ARRAY_COMMA, OBJECT_COLON
227 // and strings also in OBJECT_EMPTY, OBJECT_COMMA.
228 // VALUE STRING : , } ] Transitions to
229 // EMPTY X X -> END
230 // ARRAY_EMPTY X X @ -> ARRAY_VALUE / pop
231 // ARRAY_VALUE @ @ -> ARRAY_COMMA / pop
232 // ARRAY_COMMA X X -> ARRAY_VALUE
233 // OBJECT_EMPTY X @ -> OBJECT_KEY / pop
234 // OBJECT_KEY @ -> OBJECT_COLON
235 // OBJECT_COLON X X -> OBJECT_VALUE
236 // OBJECT_VALUE @ @ -> OBJECT_COMMA / pop
237 // OBJECT_COMMA X -> OBJECT_KEY
238 // END
239 // Starting a new array or object will push the current state. The "pop"
240 // above means restoring this state and then marking it as an ended value.
241 // X means generic handling, @ means special handling for just that
242 // state - that is, values are handled generically, only punctuation
243 // cares about the current state.
244 // Values for states are chosen so bits 0 and 1 tell whether
245 // a string/value is allowed, and setting bits 0 through 2 after a value
246 // gets to the next state (not empty, doesn't allow a value).
247
248 // State building-block constants.
249 static const int INSIDE_ARRAY = 1;
250 static const int INSIDE_OBJECT = 2;
251 static const int AFTER_COLON = 3; // Always inside object.
252
253 static const int ALLOW_STRING_MASK = 8; // Allowed if zero.
254 static const int ALLOW_VALUE_MASK = 4; // Allowed if zero.
255 static const int ALLOW_VALUE = 0;
256 static const int STRING_ONLY = 4;
257 static const int NO_VALUES = 12;
258
259 // Objects and arrays are "empty" until their first property/element.
260 static const int EMPTY = 0;
261 static const int NON_EMPTY = 16;
262 static const int EMPTY_MASK = 16; // Empty if zero.
263
264
265 static const int VALUE_READ_BITS = NO_VALUES | NON_EMPTY;
266
267 // Actual states.
268 static const int STATE_INITIAL = EMPTY | ALLOW_VALUE;
269 static const int STATE_END = NON_EMPTY | NO_VALUES;
270
271 static const int STATE_ARRAY_EMPTY = INSIDE_ARRAY | EMPTY | ALLOW_VALUE;
272 static const int STATE_ARRAY_VALUE = INSIDE_ARRAY | NON_EMPTY | NO_VALUES;
273 static const int STATE_ARRAY_COMMA = INSIDE_ARRAY | NON_EMPTY | ALLOW_VALUE;
274
275 static const int STATE_OBJECT_EMPTY = INSIDE_OBJECT | EMPTY | STRING_ONLY;
276 static const int STATE_OBJECT_KEY = INSIDE_OBJECT | NON_EMPTY | NO_VALUES;
277 static const int STATE_OBJECT_COLON = AFTER_COLON | NON_EMPTY | ALLOW_VALUE;
278 static const int STATE_OBJECT_VALUE = AFTER_COLON | NON_EMPTY | NO_VALUES;
279 static const int STATE_OBJECT_COMMA = INSIDE_OBJECT | NON_EMPTY | STRING_ONLY;
280
281 // Character code constants.
282 static const int BACKSPACE = 0x08;
283 static const int TAB = 0x09;
284 static const int NEWLINE = 0x0a;
285 static const int CARRIAGE_RETURN = 0x0d;
286 static const int FORM_FEED = 0x0c;
287 static const int SPACE = 0x20;
288 static const int QUOTE = 0x22;
289 static const int PLUS = 0x2b;
290 static const int COMMA = 0x2c;
291 static const int MINUS = 0x2d;
292 static const int DECIMALPOINT = 0x2e;
293 static const int SLASH = 0x2f;
294 static const int CHAR_0 = 0x30;
295 static const int CHAR_9 = 0x39;
296 static const int COLON = 0x3a;
297 static const int CHAR_E = 0x45;
298 static const int LBRACKET = 0x5b;
299 static const int BACKSLASH = 0x5c;
300 static const int RBRACKET = 0x5d;
301 static const int CHAR_a = 0x61;
302 static const int CHAR_b = 0x62;
303 static const int CHAR_e = 0x65;
304 static const int CHAR_f = 0x66;
305 static const int CHAR_l = 0x6c;
306 static const int CHAR_n = 0x6e;
307 static const int CHAR_r = 0x72;
308 static const int CHAR_s = 0x73;
309 static const int CHAR_t = 0x74;
310 static const int CHAR_u = 0x75;
311 static const int LBRACE = 0x7b;
312 static const int RBRACE = 0x7d;
313
314 final String source;
315 final JsonListener listener;
316 JsonParser(this.source, this.listener);
317
318 /** Parses [source], or throws if it fails. */
319 void parse() {
320 final List<int> states = <int>[];
321 int state = STATE_INITIAL;
322 int position = 0;
323 int length = source.length;
324 while (position < length) {
325 int char = source.charCodeAt(position);
326 switch (char) {
327 case SPACE:
328 case CARRIAGE_RETURN:
329 case NEWLINE:
330 case TAB:
331 position++;
332 break;
333 case QUOTE:
334 if ((state & ALLOW_STRING_MASK) != 0) fail(position);
335 position = parseString(position + 1);
336 state |= VALUE_READ_BITS;
337 break;
338 case LBRACKET:
339 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
340 listener.beginArray();
341 states.add(state);
342 state = STATE_ARRAY_EMPTY;
343 position++;
344 break;
345 case LBRACE:
346 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
347 listener.beginObject();
348 states.add(state);
349 state = STATE_OBJECT_EMPTY;
350 position++;
351 break;
352 case CHAR_n:
353 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
354 position = parseNull(position);
355 state |= VALUE_READ_BITS;
356 break;
357 case CHAR_f:
358 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
359 position = parseFalse(position);
360 state |= VALUE_READ_BITS;
361 break;
362 case CHAR_t:
363 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
364 position = parseTrue(position);
365 state |= VALUE_READ_BITS;
366 break;
367 case COLON:
368 if (state != STATE_OBJECT_KEY) fail(position);
369 listener.propertyName();
370 state = STATE_OBJECT_COLON;
371 position++;
372 break;
373 case COMMA:
374 if (state == STATE_OBJECT_VALUE) {
375 listener.propertyValue();
376 state = STATE_OBJECT_COMMA;
377 position++;
378 } else if (state == STATE_ARRAY_VALUE) {
379 listener.arrayElement();
380 state = STATE_ARRAY_COMMA;
381 position++;
382 } else {
383 fail(position);
384 }
385 break;
386 case RBRACKET:
387 if (state == STATE_ARRAY_EMPTY) {
388 listener.endArray();
389 } else if (state == STATE_ARRAY_VALUE) {
390 listener.arrayElement();
391 listener.endArray();
392 } else {
393 fail(position);
394 }
395 state = states.removeLast() | VALUE_READ_BITS;
396 position++;
397 break;
398 case RBRACE:
399 if (state == STATE_OBJECT_EMPTY) {
400 listener.endObject();
401 } else if (state == STATE_OBJECT_VALUE) {
402 listener.propertyValue();
403 listener.endObject();
404 } else {
405 fail(position);
406 }
407 state = states.removeLast() | VALUE_READ_BITS;
408 position++;
409 break;
410 default:
411 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
412 position = parseNumber(char, position);
413 state |= VALUE_READ_BITS;
414 break;
415 }
416 }
417 if (state != STATE_END) fail(position);
418 }
419
420 /**
421 * Parses a "true" literal starting at [position].
422 *
423 * [:source[position]:] must be "t".
424 */
425 int parseTrue(int position) {
426 assert(source.charCodeAt(position) == CHAR_t);
427 if (source.length < position + 4) fail(position, "Unexpected identifier");
428 if (source.charCodeAt(position + 1) != CHAR_r ||
429 source.charCodeAt(position + 2) != CHAR_u ||
430 source.charCodeAt(position + 3) != CHAR_e) {
431 fail(position);
432 }
433 listener.handleBool(true);
434 return position + 4;
435 }
436
437 /**
438 * Parses a "false" literal starting at [position].
439 *
440 * [:source[position]:] must be "f".
441 */
442 int parseFalse(int position) {
443 assert(source.charCodeAt(position) == CHAR_f);
444 if (source.length < position + 5) fail(position, "Unexpected identifier");
445 if (source.charCodeAt(position + 1) != CHAR_a ||
446 source.charCodeAt(position + 2) != CHAR_l ||
447 source.charCodeAt(position + 3) != CHAR_s ||
448 source.charCodeAt(position + 4) != CHAR_e) {
449 fail(position);
450 }
451 listener.handleBool(false);
452 return position + 5;
453 }
454
455 /** Parses a "null" literal starting at [position].
456 *
457 * [:source[position]:] must be "n".
458 */
459 int parseNull(int position) {
460 assert(source.charCodeAt(position) == CHAR_n);
461 if (source.length < position + 4) fail(position, "Unexpected identifier");
462 if (source.charCodeAt(position + 1) != CHAR_u ||
463 source.charCodeAt(position + 2) != CHAR_l ||
464 source.charCodeAt(position + 3) != CHAR_l) {
465 fail(position);
466 }
467 listener.handleNull();
468 return position + 4;
469 }
470
471 int parseString(int position) {
472 // Format: '"'([^\x00-\x1f\\\"]|'\\'[bfnrt/\\"])*'"'
473 // Initial position is right after first '"'.
474 int start = position;
475 int char;
476 do {
477 if (position == source.length) {
478 fail(start - 1, "Unterminated string");
479 }
480 char = source.charCodeAt(position);
481 if (char == QUOTE) {
482 listener.handleString(source.substring(start, position));
483 return position + 1;
484 }
485 if (char < SPACE) {
486 fail(position, "Control character in string");
487 }
488 position++;
489 } while (char != BACKSLASH);
490 // Backslash escape detected. Collect character codes for rest of string.
491 int firstEscape = position - 1;
492 List<int> chars = <int>[];
493 while (true) {
494 if (position == source.length) {
495 fail(start - 1, "Unterminated string");
496 }
497 char = source.charCodeAt(position);
498 switch (char) {
499 case CHAR_b: char = BACKSPACE; break;
500 case CHAR_f: char = FORM_FEED; break;
501 case CHAR_n: char = NEWLINE; break;
502 case CHAR_r: char = CARRIAGE_RETURN; break;
503 case CHAR_t: char = TAB; break;
504 case SLASH:
505 case BACKSLASH:
506 case QUOTE:
507 break;
508 case CHAR_u:
509 int hexStart = position - 1;
510 int value = 0;
511 for (int i = 0; i < 4; i++) {
512 position++;
513 if (position == source.length) {
514 fail(start - 1, "Unterminated string");
515 }
516 char = source.charCodeAt(position);
517 char -= 0x30;
518 if (char < 0) fail(hexStart, "Invalid unicode escape");
519 if (char < 10) {
520 value = value * 16 + char;
521 } else {
522 char = (char | 0x20) - 0x31;
523 if (char < 0 || char > 5) {
524 fail(hexStart, "Invalid unicode escape");
525 }
526 value = value * 16 + char + 10;
527 }
528 }
529 char = value;
530 break;
531 default:
532 if (char < SPACE) fail(position, "Control character in string");
533 fail(position, "Unrecognized string escape");
534 }
535 do {
536 chars.add(char);
537 position++;
538 if (position == source.length) fail(start - 1, "Unterminated string");
539 char = source.charCodeAt(position);
540 if (char == QUOTE) {
541 String result = new String.fromCharCodes(chars);
542 if (start < firstEscape) {
543 result = "${source.substring(start, firstEscape)}$result";
544 }
545 listener.handleString(result);
546 return position + 1;
547 }
548 if (char < SPACE) {
549 fail(position, "Control character in string");
550 }
551 } while (char != BACKSLASH);
552 position++;
553 }
554 }
555
556 int parseNumber(int char, int position) {
557 // Format:
558 // '-'?('0'|[1-9][0-9]*)('.'[0-9]+)?([eE][+-]?[0-9]+)?
559 int start = position;
560 int length = source.length;
561 bool isDouble = false;
562 if (char == MINUS) {
563 position++;
564 if (position == length) fail(position, "Missing expected digit");
565 char = source.charCodeAt(position);
566 }
567 if (char < CHAR_0 || char > CHAR_9) {
568 fail(position, "Missing expected digit");
569 }
570 int handleLiteral(position) {
571 String literal = source.substring(start, position);
572 // This correctly creates -0 for doubles.
573 num value = (isDouble ? double.parse(literal) : int.parse(literal));
574 listener.handleNumber(value);
575 return position;
576 }
577 if (char == CHAR_0) {
578 position++;
579 if (position == length) return handleLiteral(position);
580 char = source.charCodeAt(position);
581 if (CHAR_0 <= char && char <= CHAR_9) {
582 fail(position);
583 }
584 } else {
585 do {
586 position++;
587 if (position == length) return handleLiteral(position);
588 char = source.charCodeAt(position);
589 } while (CHAR_0 <= char && char <= CHAR_9);
590 }
591 if (char == DECIMALPOINT) {
592 isDouble = true;
593 position++;
594 if (position == length) fail(position, "Missing expected digit");
595 char = source.charCodeAt(position);
596 if (char < CHAR_0 || char > CHAR_9) fail(position);
597 do {
598 position++;
599 if (position == length) return handleLiteral(position);
600 char = source.charCodeAt(position);
601 } while (CHAR_0 <= char && char <= CHAR_9);
602 }
603 if (char == CHAR_e || char == CHAR_E) {
604 isDouble = true;
605 position++;
606 if (position == length) fail(position, "Missing expected digit");
607 char = source.charCodeAt(position);
608 if (char == PLUS || char == MINUS) {
609 position++;
610 if (position == length) fail(position, "Missing expected digit");
611 char = source.charCodeAt(position);
612 }
613 if (char < CHAR_0 || char > CHAR_9) {
614 fail(position, "Missing expected digit");
615 }
616 do {
617 position++;
618 if (position == length) return handleLiteral(position);
619 char = source.charCodeAt(position);
620 } while (CHAR_0 <= char && char <= CHAR_9);
621 }
622 return handleLiteral(position);
623 }
624
625 void fail(int position, [String message]) {
626 if (message == null) message = "Unexpected character";
627 listener.fail(source, position, message);
628 // If the listener didn't throw, do it here.
629 String slice;
630 int sliceEnd = position + 20;
631 if (sliceEnd > source.length) {
632 slice = "'${source.substring(position)}'";
633 } else {
634 slice = "'${source.substring(position, sliceEnd)}...'";
635 }
636 throw new FormatException("Unexpected character at $position: $slice");
637 }
638 }
639
640
641 class _JsonStringifier {
642 StringBuffer sb;
643 List<Object> seen; // TODO: that should be identity set.
644
645 _JsonStringifier(this.sb) : seen = [];
646
647 static String stringify(final object) {
648 StringBuffer output = new StringBuffer();
649 _JsonStringifier stringifier = new _JsonStringifier(output);
650 stringifier.stringifyValue(object);
651 return output.toString();
652 }
653
654 static void printOn(final object, StringBuffer output) {
655 _JsonStringifier stringifier = new _JsonStringifier(output);
656 stringifier.stringifyValue(object);
657 }
658
659 static String numberToString(num x) {
660 return x.toString();
661 }
662
663 // ('0' + x) or ('a' + x - 10)
664 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x;
665
666 static void escape(StringBuffer sb, String s) {
667 final int length = s.length;
668 bool needsEscape = false;
669 final charCodes = new List<int>();
670 for (int i = 0; i < length; i++) {
671 int charCode = s.charCodeAt(i);
672 if (charCode < 32) {
673 needsEscape = true;
674 charCodes.add(JsonParser.BACKSLASH);
675 switch (charCode) {
676 case JsonParser.BACKSPACE:
677 charCodes.add(JsonParser.CHAR_b);
678 break;
679 case JsonParser.TAB:
680 charCodes.add(JsonParser.CHAR_t);
681 break;
682 case JsonParser.NEWLINE:
683 charCodes.add(JsonParser.CHAR_n);
684 break;
685 case JsonParser.FORM_FEED:
686 charCodes.add(JsonParser.CHAR_f);
687 break;
688 case JsonParser.CARRIAGE_RETURN:
689 charCodes.add(JsonParser.CHAR_r);
690 break;
691 default:
692 charCodes.add(JsonParser.CHAR_u);
693 charCodes.add(hexDigit((charCode >> 12) & 0xf));
694 charCodes.add(hexDigit((charCode >> 8) & 0xf));
695 charCodes.add(hexDigit((charCode >> 4) & 0xf));
696 charCodes.add(hexDigit(charCode & 0xf));
697 break;
698 }
699 } else if (charCode == JsonParser.QUOTE ||
700 charCode == JsonParser.BACKSLASH) {
701 needsEscape = true;
702 charCodes.add(JsonParser.BACKSLASH);
703 charCodes.add(charCode);
704 } else {
705 charCodes.add(charCode);
706 }
707 }
708 sb.add(needsEscape ? new String.fromCharCodes(charCodes) : s);
709 }
710
711 void checkCycle(final object) {
712 // TODO: use Iterables.
713 for (int i = 0; i < seen.length; i++) {
714 if (identical(seen[i], object)) {
715 throw 'Cyclic structure';
716 }
717 }
718 seen.add(object);
719 }
720
721 void stringifyValue(final object) {
722 // Tries stringifying object directly. If it's not a simple value, List or
723 // Map, call toJson() to get a custom representation and try serializing
724 // that.
725 if (!stringifyJsonValue(object)) {
726 checkCycle(object);
727 try {
728 var customJson = object.toJson();
729 if (!stringifyJsonValue(customJson)) {
730 throw new JsonUnsupportedObjectError(object);
731 }
732 seen.removeLast();
733 } catch (e) {
734 throw new JsonUnsupportedObjectError.withCause(object, e);
735 }
736 }
737 }
738
739 /**
740 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value.
741 *
742 * Returns true if the value is one of these types, and false if not.
743 * If a value is both a [List] and a [Map], it's serialized as a [List].
744 */
745 bool stringifyJsonValue(final object) {
746 if (object is num) {
747 // TODO: use writeOn.
748 sb.add(numberToString(object));
749 return true;
750 } else if (identical(object, true)) {
751 sb.add('true');
752 return true;
753 } else if (identical(object, false)) {
754 sb.add('false');
755 return true;
756 } else if (object == null) {
757 sb.add('null');
758 return true;
759 } else if (object is String) {
760 sb.add('"');
761 escape(sb, object);
762 sb.add('"');
763 return true;
764 } else if (object is List) {
765 checkCycle(object);
766 List a = object;
767 sb.add('[');
768 if (a.length > 0) {
769 stringifyValue(a[0]);
770 // TODO: switch to Iterables.
771 for (int i = 1; i < a.length; i++) {
772 sb.add(',');
773 stringifyValue(a[i]);
774 }
775 }
776 sb.add(']');
777 seen.removeLast();
778 return true;
779 } else if (object is Map) {
780 checkCycle(object);
781 Map<String, Object> m = object;
782 sb.add('{');
783 bool first = true;
784 m.forEach((String key, Object value) {
785 if (!first) {
786 sb.add(',"');
787 } else {
788 sb.add('"');
789 }
790 escape(sb, key);
791 sb.add('":');
792 stringifyValue(value);
793 first = false;
794 });
795 sb.add('}');
796 seen.removeLast();
797 return true;
798 } else {
799 return false;
800 }
801 }
802 }
OLDNEW
« no previous file with comments | « sdk/lib/json/json.dart ('k') | sdk/lib/json/json_sources.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698