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

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