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