Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 } |
| OLD | NEW |