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 // 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 Loading... | |
| 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 } |
| OLD | NEW |