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

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

Powered by Google App Engine
This is Rietveld 408576698