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

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

Issue 10914009: Make JSON.stringify call toJson() on objects that it can't serialize. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Made JSON.length disappear. Created 8 years, 3 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 | « no previous file | tests/json/json_test.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 #import('dart:math'); 7 #import('dart:math');
8 8
9 // Pure Dart implementation of JSON protocol. 9 // JSON parsing and serialization.
10
11 /**
12 * Error thrown by JSON serialization if an object cannot be serialized.
13 *
14 * The [unsupportedObject] field holds that object that failed to be serialized.
15 *
16 * If an isn't directly serializable, the serializer calls the 'toJson' method
17 * on the object. If that call fails, the error will be stored in the [cause]
18 * field. If the call returns an object that isn't directly serializable,
19 * the [cause] will be null.
20 */
21 class JsonUnsupportedObjectError {
22 // TODO: proper base class.
23 /** The object that could not be serialized. */
24 final unsupportedObject;
25 /** The exception thrown by object's [:toJson:] method, if any. */
26 final cause;
27 JsonUnsupportedObjectError(this.unsupportedObject) : cause = null;
28 JsonUnsupportedObjectError.withCause(this.unsupportedObject, this.cause);
29
30 String toString() {
31 if (cause != null) {
32 return "Calling toJson method on object failed.";
33 } else {
34 return "Object toJson method returns non-serializable value.";
35 }
36 }
37 }
38
10 39
11 /** 40 /**
12 * Utility class to parse JSON and serialize objects to JSON. 41 * Utility class to parse JSON and serialize objects to JSON.
13 */ 42 */
14 class JSON { 43 class JSON {
15 /** 44 /**
16 * Parses [json] and build the corresponding object. 45 * Parses [json] and build the corresponding parsed JSON value.
46 *
47 * Parsed JSON values are of the types [num], [String], [bool], [Null],
48 * [List]s of parsed JSON values or [Map]s from [String] to parsed
49 * JSON values.
50 *
51 * Throws [JSONParseException] if the input is not valid JSON text.
17 */ 52 */
18 static parse(String json) { 53 static parse(String json) {
19 return _JsonParser.parse(json); 54 return _JsonParser.parse(json);
20 } 55 }
21 56
22 /** 57 /**
23 * Checks validity of JSON source in [str] and returns its text 58 * Serializes [object] into a JSON string.
24 * length. Returns 0 if [str] does not begin with a valid JSON 59 *
25 * object. 60 * Directly serializable types are [num], [String], [bool], [Null], [List]
26 */ 61 * and [Map].
27 static int length(String str) { 62 * For [List], the elements must all be serializable.
28 return _JsonParser.objectLength(str); 63 * For [Map], the keys must be [String] and the values must be serializable.
29 } 64 * If a value is any other type is attempted serialized, a "toJson()" method
30 65 * is invoked on the object and the result, which must be a directly
31 /** 66 * serializable type, is serialized instead of the original value.
32 * Serializes [object] into JSON string. 67 * If the object does not support this method, throws, or returns a
68 * value that is not directly serializable, a [JsonUnsupportedObjectError]
69 * exception is thrown. If the call throws (including the case where there
70 * is no nullary "toJson" method, the error is caught and stored in the
71 * [JsonUnsupportedObjectError]'s [:cause:] field.
72 *
73 * Objects should not change during serialization.
74 * If an object is serialized more than once, [stringify] is allowed to cache
75 * the JSON text for it. I.e., if an object changes after it is first
76 * serialized, the new values may or may not be reflected in the result.
33 */ 77 */
34 static String stringify(Object object) { 78 static String stringify(Object object) {
35 return JsonStringifier.stringify(object); 79 return _JsonStringifier.stringify(object);
36 } 80 }
37 81
38 /** 82 /**
39 * Serializes [object] into [output] stream. 83 * Serializes [object] into [output] stream.
84 *
85 * Performs the same operations as [stringify] but outputs the resulting
86 * string to an existing [StringBuffer] instead of creating a new [String].
87 *
88 * If serialization fails by throwing, some data might have been added to
89 * [output], but it won't contain valid JSON text.
40 */ 90 */
41 static void printOn(Object object, StringBuffer output) { 91 static void printOn(Object object, StringBuffer output) {
42 return JsonStringifier.printOn(object, output); 92 return _JsonStringifier.printOn(object, output);
43 } 93 }
44 } 94 }
45 95
46 //// Implementation /////////////////////////////////////////////////////////// 96 //// Implementation ///////////////////////////////////////////////////////////
47 97
48 // TODO(ajohnsen): Introduce when we have a common exception interface for json. 98 // TODO(ajohnsen): Introduce when we have a common exception interface for json.
49 class JSONParseException { 99 class JSONParseException {
50 JSONParseException(int position, String message) : 100 JSONParseException(int position, String message) :
51 position = position, 101 position = position,
52 message = 'JSONParseException: $message, at offset $position'; 102 message = 'JSONParseException: $message, at offset $position';
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
104 static const int WHITESPACE = SPACE; 154 static const int WHITESPACE = SPACE;
105 155
106 static const int LAST_ASCII = RBRACE; 156 static const int LAST_ASCII = RBRACE;
107 157
108 static const String NULL_STRING = "null"; 158 static const String NULL_STRING = "null";
109 static const String TRUE_STRING = "true"; 159 static const String TRUE_STRING = "true";
110 static const String FALSE_STRING = "false"; 160 static const String FALSE_STRING = "false";
111 161
112 162
113 static parse(String json) { 163 static parse(String json) {
114 return new _JsonParser._internal(json)._parseToplevel(); 164 return new _JsonParser(json).parseToplevel();
115 } 165 }
116 166
117 static objectLength(String str) { 167 _JsonParser(String json)
118 var p = new _JsonParser._internal(str);
119 var firstToken = p._token();
120 if (firstToken != LBRACE) {
121 return 0;
122 }
123 try {
124 p._parseObject();
125 assert(p.position <= p.length);
126 return p.position;
127 } catch (e) {
128 return 0;
129 }
130 }
131
132 _JsonParser._internal(String json)
133 : json = json, 168 : json = json,
134 length = json.length { 169 length = json.length {
135 if (tokens !== null) return; 170 if (tokens !== null) return;
136 171
137 // Use a list as jump-table, faster then switch and if. 172 // Use a list as jump-table. It is faster than switch and if.
138 tokens = new List<int>(LAST_ASCII + 1); 173 tokens = new List<int>(LAST_ASCII + 1);
139 tokens[TAB] = WHITESPACE; 174 tokens[TAB] = WHITESPACE;
140 tokens[NEW_LINE] = WHITESPACE; 175 tokens[NEW_LINE] = WHITESPACE;
141 tokens[CARRIAGE_RETURN] = WHITESPACE; 176 tokens[CARRIAGE_RETURN] = WHITESPACE;
142 tokens[SPACE] = WHITESPACE; 177 tokens[SPACE] = WHITESPACE;
143 tokens[CHAR_0] = NUMBER_LITERAL; 178 tokens[CHAR_0] = NUMBER_LITERAL;
144 tokens[CHAR_1] = NUMBER_LITERAL; 179 tokens[CHAR_1] = NUMBER_LITERAL;
145 tokens[CHAR_2] = NUMBER_LITERAL; 180 tokens[CHAR_2] = NUMBER_LITERAL;
146 tokens[CHAR_3] = NUMBER_LITERAL; 181 tokens[CHAR_3] = NUMBER_LITERAL;
147 tokens[CHAR_4] = NUMBER_LITERAL; 182 tokens[CHAR_4] = NUMBER_LITERAL;
148 tokens[CHAR_5] = NUMBER_LITERAL; 183 tokens[CHAR_5] = NUMBER_LITERAL;
149 tokens[CHAR_6] = NUMBER_LITERAL; 184 tokens[CHAR_6] = NUMBER_LITERAL;
150 tokens[CHAR_7] = NUMBER_LITERAL; 185 tokens[CHAR_7] = NUMBER_LITERAL;
151 tokens[CHAR_8] = NUMBER_LITERAL; 186 tokens[CHAR_8] = NUMBER_LITERAL;
152 tokens[CHAR_9] = NUMBER_LITERAL; 187 tokens[CHAR_9] = NUMBER_LITERAL;
153 tokens[MINUS] = NUMBER_LITERAL; 188 tokens[MINUS] = NUMBER_LITERAL;
154 tokens[LBRACE] = LBRACE; 189 tokens[LBRACE] = LBRACE;
155 tokens[RBRACE] = RBRACE; 190 tokens[RBRACE] = RBRACE;
156 tokens[LBRACKET] = LBRACKET; 191 tokens[LBRACKET] = LBRACKET;
157 tokens[RBRACKET] = RBRACKET; 192 tokens[RBRACKET] = RBRACKET;
158 tokens[QUOTE] = STRING_LITERAL; 193 tokens[QUOTE] = STRING_LITERAL;
159 tokens[COLON] = COLON; 194 tokens[COLON] = COLON;
160 tokens[COMMA] = COMMA; 195 tokens[COMMA] = COMMA;
161 tokens[CHAR_N] = NULL_LITERAL; 196 tokens[CHAR_N] = NULL_LITERAL;
162 tokens[CHAR_T] = TRUE_LITERAL; 197 tokens[CHAR_T] = TRUE_LITERAL;
163 tokens[CHAR_F] = FALSE_LITERAL; 198 tokens[CHAR_F] = FALSE_LITERAL;
164 } 199 }
165 200
166 _parseToplevel() { 201 parseToplevel() {
167 final result = _parseValue(); 202 final result = parseValue();
168 if (_token() !== null) { 203 if (token() !== null) {
169 _error('Junk at the end of JSON input'); 204 error('Junk at the end of JSON input');
170 } 205 }
171 return result; 206 return result;
172 } 207 }
173 208
174 _parseValue() { 209 parseValue() {
175 final int token = _token(); 210 final int token = token();
176 if (token === null) { 211 if (token === null) {
177 _error('Nothing to parse'); 212 error('Nothing to parse');
178 } 213 }
179 switch (token) { 214 switch (token) {
180 case STRING_LITERAL: return _parseString(); 215 case STRING_LITERAL: return parseString();
181 case NUMBER_LITERAL: return _parseNumber(); 216 case NUMBER_LITERAL: return parseNumber();
182 case NULL_LITERAL: return _expectKeyword(NULL_STRING, null); 217 case NULL_LITERAL: return expectKeyword(NULL_STRING, null);
183 case FALSE_LITERAL: return _expectKeyword(FALSE_STRING, false); 218 case FALSE_LITERAL: return expectKeyword(FALSE_STRING, false);
184 case TRUE_LITERAL: return _expectKeyword(TRUE_STRING, true); 219 case TRUE_LITERAL: return expectKeyword(TRUE_STRING, true);
185 case LBRACE: return _parseObject(); 220 case LBRACE: return parseObject();
186 case LBRACKET: return _parseList(); 221 case LBRACKET: return parseList();
187 222
188 default: 223 default:
189 _error('Unexpected token'); 224 error('Unexpected token');
190 } 225 }
191 } 226 }
192 227
193 Object _expectKeyword(String word, Object value) { 228 Object expectKeyword(String word, Object value) {
194 for (int i = 0; i < word.length; i++) { 229 for (int i = 0; i < word.length; i++) {
195 // Implicit end check in _char(). 230 // Implicit end check in char().
196 if (_char() != word.charCodeAt(i)) _error("Expected keyword '$word'"); 231 if (char() != word.charCodeAt(i)) error("Expected keyword '$word'");
197 position++; 232 position++;
198 } 233 }
199 return value; 234 return value;
200 } 235 }
201 236
202 _parseObject() { 237 parseObject() {
203 final object = {}; 238 final object = {};
204 239
205 position++; // Eat '{'. 240 position++; // Eat '{'.
206 241
207 if (!_isToken(RBRACE)) { 242 if (!isToken(RBRACE)) {
208 while (true) { 243 while (true) {
209 final String key = _parseString(); 244 final String key = parseString();
210 if (!_isToken(COLON)) _error("Expected ':' when parsing object"); 245 if (!isToken(COLON)) error("Expected ':' when parsing object");
211 position++; 246 position++;
212 object[key] = _parseValue(); 247 object[key] = parseValue();
213 248
214 if (!_isToken(COMMA)) break; 249 if (!isToken(COMMA)) break;
215 position++; // Skip ','. 250 position++; // Skip ','.
216 }; 251 };
217 252
218 if (!_isToken(RBRACE)) _error("Expected '}' at end of object"); 253 if (!isToken(RBRACE)) error("Expected '}' at end of object");
219 } 254 }
220 position++; 255 position++;
221 256
222 return object; 257 return object;
223 } 258 }
224 259
225 _parseList() { 260 parseList() {
226 final list = []; 261 final list = [];
227 262
228 position++; // Eat '['. 263 position++; // Eat '['.
229 264
230 if (!_isToken(RBRACKET)) { 265 if (!isToken(RBRACKET)) {
231 while (true) { 266 while (true) {
232 list.add(_parseValue()); 267 list.add(parseValue());
233 268
234 if (!_isToken(COMMA)) break; 269 if (!isToken(COMMA)) break;
235 position++; 270 position++;
236 }; 271 };
237 272
238 if (!_isToken(RBRACKET)) _error("Expected ']' at end of list"); 273 if (!isToken(RBRACKET)) error("Expected ']' at end of list");
239 } 274 }
240 position++; 275 position++;
241 276
242 return list; 277 return list;
243 } 278 }
244 279
245 String _parseString() { 280 String parseString() {
246 if (!_isToken(STRING_LITERAL)) _error("Expected string literal"); 281 if (!isToken(STRING_LITERAL)) error("Expected string literal");
247 282
248 position++; // Eat '"'. 283 position++; // Eat '"'.
249 284
250 List<int> charCodes = new List<int>(); 285 List<int> charCodes = new List<int>();
251 while (true) { 286 while (true) {
252 int c = _char(); 287 int c = char();
253 if (c == QUOTE) { 288 if (c == QUOTE) {
254 position++; 289 position++;
255 break; 290 break;
256 } 291 }
257 if (c == BACKSLASH) { 292 if (c == BACKSLASH) {
258 position++; 293 position++;
259 if (position == length) { 294 if (position == length) {
260 _error('\\ at the end of input'); 295 error('\\ at the end of input');
261 } 296 }
262 297
263 switch (_char()) { 298 switch (char()) {
264 case QUOTE: 299 case QUOTE:
265 c = QUOTE; 300 c = QUOTE;
266 break; 301 break;
267 case BACKSLASH: 302 case BACKSLASH:
268 c = BACKSLASH; 303 c = BACKSLASH;
269 break; 304 break;
270 case SLASH: 305 case SLASH:
271 c = SLASH; 306 c = SLASH;
272 break; 307 break;
273 case CHAR_B: 308 case CHAR_B:
274 c = BACKSPACE; 309 c = BACKSPACE;
275 break; 310 break;
276 case CHAR_N: 311 case CHAR_N:
277 c = NEW_LINE; 312 c = NEW_LINE;
278 break; 313 break;
279 case CHAR_R: 314 case CHAR_R:
280 c = CARRIAGE_RETURN; 315 c = CARRIAGE_RETURN;
281 break; 316 break;
282 case CHAR_F: 317 case CHAR_F:
283 c = FORM_FEED; 318 c = FORM_FEED;
284 break; 319 break;
285 case CHAR_T: 320 case CHAR_T:
286 c = TAB; 321 c = TAB;
287 break; 322 break;
288 case CHAR_U: 323 case CHAR_U:
289 if (position + 5 > length) { 324 if (position + 5 > length) {
290 _error('Invalid unicode esacape sequence'); 325 error('Invalid unicode esacape sequence');
291 } 326 }
292 final codeString = json.substring(position + 1, position + 5); 327 final codeString = json.substring(position + 1, position + 5);
293 try { 328 try {
294 c = parseInt('0x${codeString}'); 329 c = parseInt('0x${codeString}');
295 } catch (e) { 330 } catch (e) {
296 _error('Invalid unicode esacape sequence'); 331 error('Invalid unicode esacape sequence');
297 } 332 }
298 position += 4; 333 position += 4;
299 break; 334 break;
300 default: 335 default:
301 _error('Invalid esacape sequence in string literal'); 336 error('Invalid esacape sequence in string literal');
302 } 337 }
303 } 338 }
304 charCodes.add(c); 339 charCodes.add(c);
305 position++; 340 position++;
306 } 341 }
307 342
308 return new String.fromCharCodes(charCodes); 343 return new String.fromCharCodes(charCodes);
309 } 344 }
310 345
311 num _parseNumber() { 346 num parseNumber() {
312 if (!_isToken(NUMBER_LITERAL)) _error('Expected number literal'); 347 if (!isToken(NUMBER_LITERAL)) error('Expected number literal');
313 348
314 final int startPos = position; 349 final int startPos = position;
315 int char = _char(); 350 int char = char();
316 if (char === MINUS) char = _nextChar(); 351 if (char === MINUS) char = nextChar();
317 if (char === CHAR_0) { 352 if (char === CHAR_0) {
318 char = _nextChar(); 353 char = nextChar();
319 } else if (_isDigit(char)) { 354 } else if (isDigit(char)) {
320 char = _nextChar(); 355 char = nextChar();
321 while (_isDigit(char)) char = _nextChar(); 356 while (isDigit(char)) char = nextChar();
322 } else { 357 } else {
323 _error('Expected digit when parsing number'); 358 error('Expected digit when parsing number');
324 } 359 }
325 360
326 bool isInt = true; 361 bool isInt = true;
327 if (char === DOT) { 362 if (char === DOT) {
328 char = _nextChar(); 363 char = nextChar();
329 if (_isDigit(char)) { 364 if (isDigit(char)) {
330 char = _nextChar(); 365 char = nextChar();
331 isInt = false; 366 isInt = false;
332 while (_isDigit(char)) char = _nextChar(); 367 while (isDigit(char)) char = nextChar();
333 } else { 368 } else {
334 _error('Expected digit following comma'); 369 error('Expected digit following comma');
335 } 370 }
336 } 371 }
337 372
338 if (char === CHAR_E || char === CHAR_CAPITAL_E) { 373 if (char === CHAR_E || char === CHAR_CAPITAL_E) {
339 char = _nextChar(); 374 char = nextChar();
340 if (char === MINUS || char === PLUS) char = _nextChar(); 375 if (char === MINUS || char === PLUS) char = nextChar();
341 if (_isDigit(char)) { 376 if (isDigit(char)) {
342 char = _nextChar(); 377 char = nextChar();
343 isInt = false; 378 isInt = false;
344 while (_isDigit(char)) char = _nextChar(); 379 while (isDigit(char)) char = nextChar();
345 } else { 380 } else {
346 _error('Expected digit following \'e\' or \'E\''); 381 error('Expected digit following \'e\' or \'E\'');
347 } 382 }
348 } 383 }
349 384
350 String number = json.substring(startPos, position); 385 String number = json.substring(startPos, position);
351 if (isInt) { 386 if (isInt) {
352 return parseInt(number); 387 return parseInt(number);
353 } else { 388 } else {
354 return parseDouble(number); 389 return parseDouble(number);
355 } 390 }
356 } 391 }
357 392
358 bool _isChar(int char) { 393 bool isChar(int char) {
359 if (position >= length) return false; 394 if (position >= length) return false;
360 return json.charCodeAt(position) == char; 395 return json.charCodeAt(position) == char;
361 } 396 }
362 397
363 bool _isDigit(int char) { 398 bool isDigit(int char) {
364 return char >= CHAR_0 && char <= CHAR_9; 399 return char >= CHAR_0 && char <= CHAR_9;
365 } 400 }
366 401
367 bool _isToken(int tokenKind) => _token() == tokenKind; 402 bool isToken(int tokenKind) => token() == tokenKind;
368 403
369 int _char() { 404 int char() {
370 if (position >= length) { 405 if (position >= length) {
371 _error('Unexpected end of JSON stream'); 406 error('Unexpected end of JSON stream');
372 } 407 }
373 return json.charCodeAt(position); 408 return json.charCodeAt(position);
374 } 409 }
375 410
376 int _nextChar() { 411 int nextChar() {
377 position++; 412 position++;
378 if (position >= length) return 0; 413 if (position >= length) return 0;
379 return json.charCodeAt(position); 414 return json.charCodeAt(position);
380 } 415 }
381 416
382 int _token() { 417 int token() {
383 while (true) { 418 while (true) {
384 if (position >= length) return null; 419 if (position >= length) return null;
385 int char = json.charCodeAt(position); 420 int char = json.charCodeAt(position);
386 int token = tokens[char]; 421 int token = tokens[char];
387 if (token === WHITESPACE) { 422 if (token === WHITESPACE) {
388 position++; 423 position++;
389 continue; 424 continue;
390 } 425 }
391 if (token === null) return 0; 426 if (token === null) return 0;
392 return token; 427 return token;
393 } 428 }
394 } 429 }
395 430
396 void _error(String message) { 431 void error(String message) {
397 throw message; 432 throw message;
398 } 433 }
399 434
400 final String json; 435 final String json;
Lasse Reichstein Nielsen 2012/09/12 10:19:40 I'll move the fields to the top of the class here
401 final int length; 436 final int length;
402 int position = 0; 437 int position = 0;
403 static List<int> tokens; 438 static List<int> tokens;
404 } 439 }
405 440
406 // TODO: proper base class. 441 class _JsonStringifier {
407 class JsonUnsupportedObjectType { 442 StringBuffer sb;
408 const JsonUnsupportedObjectType(); 443 List<Object> seen; // TODO: that should be identity set.
409 }
410 444
411 class JsonStringifier { 445 _JsonStringifier(this.sb) : seen = [];
446
412 static String stringify(final object) { 447 static String stringify(final object) {
413 StringBuffer output = new StringBuffer(); 448 StringBuffer output = new StringBuffer();
414 JsonStringifier stringifier = new JsonStringifier._internal(output); 449 _JsonStringifier stringifier = new _JsonStringifier(output);
415 stringifier._stringify(object); 450 stringifier.stringifyValue(object);
416 return output.toString(); 451 return output.toString();
417 } 452 }
418 453
419 static void printOn(final object, StringBuffer output) { 454 static void printOn(final object, StringBuffer output) {
420 JsonStringifier stringifier = new JsonStringifier._internal(output); 455 _JsonStringifier stringifier = new _JsonStringifier(output);
421 stringifier._stringify(object); 456 stringifier.stringifyValue(object);
422 } 457 }
423 458
424 JsonStringifier._internal(this._sb) 459 static String numberToString(num x) {
425 : _seen = []; 460 // Double values should create a representation with sufficient digits to
426 461 // create the same value again. I.e., such that the original double value
427 StringBuffer _sb; 462 // is the closest representable double value to the exact mathematical
428 List<Object> _seen; // TODO: that should be identity set. 463 // value of the string representation.
Lasse Reichstein Nielsen 2012/09/12 10:19:40 I forgot to remove this comment. Will do before co
429 464 return x.toString();
430 static String _numberToString(num x) {
431 // TODO: need some more investigation what to do with precision
432 // of double values.
433 if (x is int) {
434 return x.toString();
435 } else if (x is double) {
436 return x.toString();
437 } else {
438 return x.toDouble().toString();
439 }
440 } 465 }
441 466
442 // ('0' + x) or ('a' + x - 10) 467 // ('0' + x) or ('a' + x - 10)
443 static int _hexDigit(int x) => x < 10 ? 48 + x : 87 + x; 468 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x;
444 469
445 static void _escape(StringBuffer sb, String s) { 470 static void escape(StringBuffer sb, String s) {
446 final int length = s.length; 471 final int length = s.length;
447 bool needsEscape = false; 472 bool needsEscape = false;
448 final charCodes = new List<int>(); 473 final charCodes = new List<int>();
449 for (int i = 0; i < length; i++) { 474 for (int i = 0; i < length; i++) {
450 int charCode = s.charCodeAt(i); 475 int charCode = s.charCodeAt(i);
451 if (charCode < 32) { 476 if (charCode < 32) {
452 needsEscape = true; 477 needsEscape = true;
453 charCodes.add(_JsonParser.BACKSLASH); 478 charCodes.add(_JsonParser.BACKSLASH);
454 switch (charCode) { 479 switch (charCode) {
455 case _JsonParser.BACKSPACE: 480 case _JsonParser.BACKSPACE:
456 charCodes.add(_JsonParser.CHAR_B); 481 charCodes.add(_JsonParser.CHAR_B);
457 break; 482 break;
458 case _JsonParser.TAB: 483 case _JsonParser.TAB:
459 charCodes.add(_JsonParser.CHAR_T); 484 charCodes.add(_JsonParser.CHAR_T);
460 break; 485 break;
461 case _JsonParser.NEW_LINE: 486 case _JsonParser.NEW_LINE:
462 charCodes.add(_JsonParser.CHAR_N); 487 charCodes.add(_JsonParser.CHAR_N);
463 break; 488 break;
464 case _JsonParser.FORM_FEED: 489 case _JsonParser.FORM_FEED:
465 charCodes.add(_JsonParser.CHAR_F); 490 charCodes.add(_JsonParser.CHAR_F);
466 break; 491 break;
467 case _JsonParser.CARRIAGE_RETURN: 492 case _JsonParser.CARRIAGE_RETURN:
468 charCodes.add(_JsonParser.CHAR_R); 493 charCodes.add(_JsonParser.CHAR_R);
469 break; 494 break;
470 default: 495 default:
471 charCodes.add(_JsonParser.CHAR_U); 496 charCodes.add(_JsonParser.CHAR_U);
472 charCodes.add(_hexDigit((charCode >> 12) & 0xf)); 497 charCodes.add(hexDigit((charCode >> 12) & 0xf));
473 charCodes.add(_hexDigit((charCode >> 8) & 0xf)); 498 charCodes.add(hexDigit((charCode >> 8) & 0xf));
474 charCodes.add(_hexDigit((charCode >> 4) & 0xf)); 499 charCodes.add(hexDigit((charCode >> 4) & 0xf));
475 charCodes.add(_hexDigit(charCode & 0xf)); 500 charCodes.add(hexDigit(charCode & 0xf));
476 break; 501 break;
477 } 502 }
478 } else if (charCode == _JsonParser.QUOTE || 503 } else if (charCode == _JsonParser.QUOTE ||
479 charCode == _JsonParser.BACKSLASH) { 504 charCode == _JsonParser.BACKSLASH) {
480 needsEscape = true; 505 needsEscape = true;
481 charCodes.add(_JsonParser.BACKSLASH); 506 charCodes.add(_JsonParser.BACKSLASH);
482 charCodes.add(charCode); 507 charCodes.add(charCode);
483 } else { 508 } else {
484 charCodes.add(charCode); 509 charCodes.add(charCode);
485 } 510 }
486 } 511 }
487 sb.add(needsEscape ? new String.fromCharCodes(charCodes) : s); 512 sb.add(needsEscape ? new String.fromCharCodes(charCodes) : s);
488 } 513 }
489 514
490 void _checkCycle(final object) { 515 void checkCycle(final object) {
491 // TODO: use Iterables. 516 // TODO: use Iterables.
492 for (int i = 0; i < _seen.length; i++) { 517 for (int i = 0; i < seen.length; i++) {
493 if (_seen[i] === object) { 518 if (seen[i] === object) {
494 throw 'Cyclic structure'; 519 throw 'Cyclic structure';
495 } 520 }
496 } 521 }
497 _seen.add(object); 522 seen.add(object);
498 } 523 }
499 524
500 void _stringify(final object) { 525 void stringifyValue(final object) {
526 // Tries stringifying object directly. If it's not a simple value, List or
527 // Map, call toJson() to get a custom representation and try serializing
528 // that.
529 if (!stringifyJsonValue(object)) {
530 checkCycle(object);
531 try {
532 var customJson = object.toJson();
533 if (!stringifyJsonValue(customJson)) {
534 throw new JsonUnsupportedObjectError(object);
535 }
536 seen.removeLast();
537 } catch (e) {
538 throw new JsonUnsupportedObjectError.withCause(object, e);
539 }
540 }
541 }
542
543 /**
544 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value.
545 *
546 * Returns true if the value is one of these types, and false if not.
547 * If a value is both a [List] and a [Map], it's serialized as a [List].
548 */
549 bool stringifyJsonValue(final object) {
501 if (object is num) { 550 if (object is num) {
502 // TODO: use writeOn. 551 // TODO: use writeOn.
503 _sb.add(_numberToString(object)); 552 sb.add(numberToString(object));
504 return; 553 return true;
505 } else if (object === true) { 554 } else if (object === true) {
506 _sb.add('true'); 555 sb.add('true');
507 return; 556 return true;
508 } else if (object === false) { 557 } else if (object === false) {
509 _sb.add('false'); 558 sb.add('false');
510 return; 559 return true;
511 } else if (object === null) { 560 } else if (object === null) {
512 _sb.add('null'); 561 sb.add('null');
513 return; 562 return true;
514 } else if (object is String) { 563 } else if (object is String) {
515 _sb.add('"'); 564 sb.add('"');
516 _escape(_sb, object); 565 escape(sb, object);
517 _sb.add('"'); 566 sb.add('"');
518 return; 567 return true;
519 } else if (object is List) { 568 } else if (object is List) {
520 _checkCycle(object); 569 checkCycle(object);
521 List a = object; 570 List a = object;
522 _sb.add('['); 571 sb.add('[');
523 if (a.length > 0) { 572 if (a.length > 0) {
524 _stringify(a[0]); 573 stringifyValue(a[0]);
525 // TODO: switch to Iterables. 574 // TODO: switch to Iterables.
526 for (int i = 1; i < a.length; i++) { 575 for (int i = 1; i < a.length; i++) {
527 _sb.add(','); 576 sb.add(',');
528 _stringify(a[i]); 577 stringifyValue(a[i]);
529 } 578 }
530 } 579 }
531 _sb.add(']'); 580 sb.add(']');
532 _seen.removeLast(); 581 seen.removeLast();
533 return; 582 return true;
534 } else if (object is Map) { 583 } else if (object is Map) {
535 _checkCycle(object); 584 checkCycle(object);
536 Map<String, Object> m = object; 585 Map<String, Object> m = object;
537 _sb.add('{'); 586 sb.add('{');
538 bool first = true; 587 bool first = true;
539 m.forEach((String key, Object value) { 588 m.forEach((String key, Object value) {
540 if (!first) { 589 if (!first) {
541 _sb.add(',"'); 590 sb.add(',"');
542 } else { 591 } else {
543 _sb.add('"'); 592 sb.add('"');
544 } 593 }
545 _escape(_sb, key); 594 escape(sb, key);
546 _sb.add('":'); 595 sb.add('":');
547 _stringify(value); 596 stringifyValue(value);
548 first = false; 597 first = false;
549 }); 598 });
550 _sb.add('}'); 599 sb.add('}');
551 _seen.removeLast(); 600 seen.removeLast();
552 return; 601 return true;
553 } else { 602 } else {
554 throw const JsonUnsupportedObjectType(); 603 return false;
555 } 604 }
556 } 605 }
557 } 606 }
OLDNEW
« no previous file with comments | « no previous file | tests/json/json_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698