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