| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 /** | 5 // JSON conversion. |
| 6 * Utilities for encoding and decoding JSON (JavaScript Object Notation) data. | |
| 7 */ | |
| 8 | 6 |
| 9 library json; | 7 patch _parseJson(String json, reviver(var key, var value)) { |
| 10 | 8 _BuildJsonListener listener; |
| 11 // JSON parsing and serialization. | 9 if (reviver == null) { |
| 12 | 10 listener = new _BuildJsonListener(); |
| 13 /** | 11 } else { |
| 14 * Error thrown by JSON serialization if an object cannot be serialized. | 12 listener = new _ReviverJsonListener(reviver); |
| 15 * | |
| 16 * The [unsupportedObject] field holds that object that failed to be serialized. | |
| 17 * | |
| 18 * If an object isn't directly serializable, the serializer calls the 'toJson' | |
| 19 * method on the object. If that call fails, the error will be stored in the | |
| 20 * [cause] field. If the call returns an object that isn't directly | |
| 21 * serializable, the [cause] will be null. | |
| 22 */ | |
| 23 class JsonUnsupportedObjectError extends Error { | |
| 24 /** The object that could not be serialized. */ | |
| 25 final unsupportedObject; | |
| 26 /** The exception thrown by object's [:toJson:] method, if any. */ | |
| 27 final cause; | |
| 28 | |
| 29 JsonUnsupportedObjectError(this.unsupportedObject, { this.cause }); | |
| 30 | |
| 31 String toString() { | |
| 32 if (cause != null) { | |
| 33 return "Calling toJson method on object failed."; | |
| 34 } else { | |
| 35 return "Object toJson method returns non-serializable value."; | |
| 36 } | |
| 37 } | 13 } |
| 38 } | 14 new _JsonParser(json, listener).parse(); |
| 39 | |
| 40 | |
| 41 /** | |
| 42 * Reports that an object could not be stringified due to cyclic references. | |
| 43 * | |
| 44 * An object that references itself cannot be serialized by [stringify]. | |
| 45 * When the cycle is detected, a [JsonCyclicError] is thrown. | |
| 46 */ | |
| 47 class JsonCyclicError extends JsonUnsupportedObjectError { | |
| 48 /** The first object that was detected as part of a cycle. */ | |
| 49 JsonCyclicError(Object object): super(object); | |
| 50 String toString() => "Cyclic error in JSON stringify"; | |
| 51 } | |
| 52 | |
| 53 | |
| 54 /** | |
| 55 * Parses [json] and build the corresponding parsed JSON value. | |
| 56 * | |
| 57 * Parsed JSON values are of the types [num], [String], [bool], [Null], | |
| 58 * [List]s of parsed JSON values or [Map]s from [String] to parsed | |
| 59 * JSON values. | |
| 60 * | |
| 61 * The optional [reviver] function, if provided, is called once for each | |
| 62 * object or list property parsed. The arguments are the property name | |
| 63 * ([String]) or list index ([int]), and the value is the parsed value. | |
| 64 * The return value of the reviver will be used as the value of that property | |
| 65 * instead the parsed value. | |
| 66 * | |
| 67 * Throws [FormatException] if the input is not valid JSON text. | |
| 68 */ | |
| 69 parse(String json, [reviver(var key, var value)]) { | |
| 70 BuildJsonListener listener; | |
| 71 if (reviver == null) { | |
| 72 listener = new BuildJsonListener(); | |
| 73 } else { | |
| 74 listener = new ReviverJsonListener(reviver); | |
| 75 } | |
| 76 new JsonParser(json, listener).parse(); | |
| 77 return listener.result; | 15 return listener.result; |
| 78 } | 16 } |
| 79 | 17 |
| 80 /** | |
| 81 * Serializes [object] into a JSON string. | |
| 82 * | |
| 83 * Directly serializable values are [num], [String], [bool], and [Null], as well | |
| 84 * as some [List] and [Map] values. | |
| 85 * For [List], the elements must all be serializable. | |
| 86 * For [Map], the keys must be [String] and the values must be serializable. | |
| 87 * | |
| 88 * If a value is any other type is attempted serialized, a "toJson()" method | |
| 89 * is invoked on the object and the result, which must be a directly | |
| 90 * serializable value, is serialized instead of the original value. | |
| 91 * | |
| 92 * If the object does not support this method, throws, or returns a | |
| 93 * value that is not directly serializable, a [JsonUnsupportedObjectError] | |
| 94 * exception is thrown. If the call throws (including the case where there | |
| 95 * is no nullary "toJson" method, the error is caught and stored in the | |
| 96 * [JsonUnsupportedObjectError]'s [:cause:] field. | |
| 97 * | |
| 98 * If a [List] or [Map] contains a reference to itself, directly or through | |
| 99 * other lists or maps, it cannot be serialized and a [JsonCyclicError] is | |
| 100 * thrown. | |
| 101 * | |
| 102 * Json Objects should not change during serialization. | |
| 103 * If an object is serialized more than once, [stringify] is allowed to cache | |
| 104 * the JSON text for it. I.e., if an object changes after it is first | |
| 105 * serialized, the new values may or may not be reflected in the result. | |
| 106 */ | |
| 107 String stringify(Object object) { | |
| 108 return _JsonStringifier.stringify(object); | |
| 109 } | |
| 110 | |
| 111 /** | |
| 112 * Serializes [object] into [output] stream. | |
| 113 * | |
| 114 * Performs the same operations as [stringify] but outputs the resulting | |
| 115 * string to an existing [StringSink] instead of creating a new [String]. | |
| 116 * | |
| 117 * If serialization fails by throwing, some data might have been added to | |
| 118 * [output], but it won't contain valid JSON text. | |
| 119 */ | |
| 120 void printOn(Object object, StringSink output) { | |
| 121 return _JsonStringifier.printOn(object, output); | |
| 122 } | |
| 123 | |
| 124 //// Implementation /////////////////////////////////////////////////////////// | 18 //// Implementation /////////////////////////////////////////////////////////// |
| 125 | 19 |
| 126 // Simple API for JSON parsing. | 20 // Simple API for JSON parsing. |
| 127 | 21 |
| 128 abstract class JsonListener { | 22 abstract class _JsonListener { |
| 129 void handleString(String value) {} | 23 void handleString(String value) {} |
| 130 void handleNumber(num value) {} | 24 void handleNumber(num value) {} |
| 131 void handleBool(bool value) {} | 25 void handleBool(bool value) {} |
| 132 void handleNull() {} | 26 void handleNull() {} |
| 133 void beginObject() {} | 27 void beginObject() {} |
| 134 void propertyName() {} | 28 void propertyName() {} |
| 135 void propertyValue() {} | 29 void propertyValue() {} |
| 136 void endObject() {} | 30 void endObject() {} |
| 137 void beginArray() {} | 31 void beginArray() {} |
| 138 void arrayElement() {} | 32 void arrayElement() {} |
| 139 void endArray() {} | 33 void endArray() {} |
| 140 /** Called on failure to parse [source]. */ | 34 /** Called on failure to parse [source]. */ |
| 141 void fail(String source, int position, String message) {} | 35 void fail(String source, int position, String message) {} |
| 142 } | 36 } |
| 143 | 37 |
| 144 /** | 38 /** |
| 145 * A [JsonListener] that builds data objects from the parser events. | 39 * A [JsonListener] that builds data objects from the parser events. |
| 146 * | 40 * |
| 147 * This is a simple stack-based object builder. It keeps the most recently | 41 * This is a simple stack-based object builder. It keeps the most recently |
| 148 * seen value in a variable, and uses it depending on the following event. | 42 * seen value in a variable, and uses it depending on the following event. |
| 149 */ | 43 */ |
| 150 class BuildJsonListener extends JsonListener { | 44 class _BuildJsonListener extends _JsonListener { |
| 151 /** | 45 /** |
| 152 * Stack used to handle nested containers. | 46 * Stack used to handle nested containers. |
| 153 * | 47 * |
| 154 * The current container is pushed on the stack when a new one is | 48 * The current container is pushed on the stack when a new one is |
| 155 * started. If the container is a [Map], there is also a current [key] | 49 * started. If the container is a [Map], there is also a current [key] |
| 156 * which is also stored on the stack. | 50 * which is also stored on the stack. |
| 157 */ | 51 */ |
| 158 List stack = []; | 52 List stack = []; |
| 159 /** The current [Map] or [List] being built. */ | 53 /** The current [Map] or [List] being built. */ |
| 160 var currentContainer; | 54 var currentContainer; |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 216 popContainer(); | 110 popContainer(); |
| 217 } | 111 } |
| 218 | 112 |
| 219 /** Read out the final result of parsing a JSON string. */ | 113 /** Read out the final result of parsing a JSON string. */ |
| 220 get result { | 114 get result { |
| 221 assert(currentContainer == null); | 115 assert(currentContainer == null); |
| 222 return value; | 116 return value; |
| 223 } | 117 } |
| 224 } | 118 } |
| 225 | 119 |
| 226 typedef _Reviver(var key, var value); | 120 class _ReviverJsonListener extends _BuildJsonListener { |
| 227 | |
| 228 class ReviverJsonListener extends BuildJsonListener { | |
| 229 final _Reviver reviver; | 121 final _Reviver reviver; |
| 230 ReviverJsonListener(reviver(key, value)) : this.reviver = reviver; | 122 _ReviverJsonListener(reviver(key, value)) : this.reviver = reviver; |
| 231 | 123 |
| 232 void arrayElement() { | 124 void arrayElement() { |
| 233 List list = currentContainer; | 125 List list = currentContainer; |
| 234 value = reviver(list.length, value); | 126 value = reviver(list.length, value); |
| 235 super.arrayElement(); | 127 super.arrayElement(); |
| 236 } | 128 } |
| 237 | 129 |
| 238 void propertyValue() { | 130 void propertyValue() { |
| 239 value = reviver(key, value); | 131 value = reviver(key, value); |
| 240 super.propertyValue(); | 132 super.propertyValue(); |
| 241 } | 133 } |
| 242 | 134 |
| 243 get result { | 135 get result { |
| 244 return reviver("", value); | 136 return reviver("", value); |
| 245 } | 137 } |
| 246 } | 138 } |
| 247 | 139 |
| 248 class JsonParser { | 140 class _JsonParser { |
| 249 // A simple non-recursive state-based parser for JSON. | 141 // A simple non-recursive state-based parser for JSON. |
| 250 // | 142 // |
| 251 // Literal values accepted in states ARRAY_EMPTY, ARRAY_COMMA, OBJECT_COLON | 143 // Literal values accepted in states ARRAY_EMPTY, ARRAY_COMMA, OBJECT_COLON |
| 252 // and strings also in OBJECT_EMPTY, OBJECT_COMMA. | 144 // and strings also in OBJECT_EMPTY, OBJECT_COMMA. |
| 253 // VALUE STRING : , } ] Transitions to | 145 // VALUE STRING : , } ] Transitions to |
| 254 // EMPTY X X -> END | 146 // EMPTY X X -> END |
| 255 // ARRAY_EMPTY X X @ -> ARRAY_VALUE / pop | 147 // ARRAY_EMPTY X X @ -> ARRAY_VALUE / pop |
| 256 // ARRAY_VALUE @ @ -> ARRAY_COMMA / pop | 148 // ARRAY_VALUE @ @ -> ARRAY_COMMA / pop |
| 257 // ARRAY_COMMA X X -> ARRAY_VALUE | 149 // ARRAY_COMMA X X -> ARRAY_VALUE |
| 258 // OBJECT_EMPTY X @ -> OBJECT_KEY / pop | 150 // OBJECT_EMPTY X @ -> OBJECT_KEY / pop |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 330 static const int CHAR_l = 0x6c; | 222 static const int CHAR_l = 0x6c; |
| 331 static const int CHAR_n = 0x6e; | 223 static const int CHAR_n = 0x6e; |
| 332 static const int CHAR_r = 0x72; | 224 static const int CHAR_r = 0x72; |
| 333 static const int CHAR_s = 0x73; | 225 static const int CHAR_s = 0x73; |
| 334 static const int CHAR_t = 0x74; | 226 static const int CHAR_t = 0x74; |
| 335 static const int CHAR_u = 0x75; | 227 static const int CHAR_u = 0x75; |
| 336 static const int LBRACE = 0x7b; | 228 static const int LBRACE = 0x7b; |
| 337 static const int RBRACE = 0x7d; | 229 static const int RBRACE = 0x7d; |
| 338 | 230 |
| 339 final String source; | 231 final String source; |
| 340 final JsonListener listener; | 232 final _JsonListener listener; |
| 341 JsonParser(this.source, this.listener); | 233 _JsonParser(this.source, this.listener); |
| 342 | 234 |
| 343 /** Parses [source], or throws if it fails. */ | 235 /** Parses [source], or throws if it fails. */ |
| 344 void parse() { | 236 void parse() { |
| 345 final List<int> states = <int>[]; | 237 final List<int> states = <int>[]; |
| 346 int state = STATE_INITIAL; | 238 int state = STATE_INITIAL; |
| 347 int position = 0; | 239 int position = 0; |
| 348 int length = source.length; | 240 int length = source.length; |
| 349 while (position < length) { | 241 while (position < length) { |
| 350 int char = source.codeUnitAt(position); | 242 int char = source.codeUnitAt(position); |
| 351 switch (char) { | 243 switch (char) { |
| (...skipping 303 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 655 String slice; | 547 String slice; |
| 656 int sliceEnd = position + 20; | 548 int sliceEnd = position + 20; |
| 657 if (sliceEnd > source.length) { | 549 if (sliceEnd > source.length) { |
| 658 slice = "'${source.substring(position)}'"; | 550 slice = "'${source.substring(position)}'"; |
| 659 } else { | 551 } else { |
| 660 slice = "'${source.substring(position, sliceEnd)}...'"; | 552 slice = "'${source.substring(position, sliceEnd)}...'"; |
| 661 } | 553 } |
| 662 throw new FormatException("Unexpected character at $position: $slice"); | 554 throw new FormatException("Unexpected character at $position: $slice"); |
| 663 } | 555 } |
| 664 } | 556 } |
| 665 | |
| 666 | |
| 667 class _JsonStringifier { | |
| 668 StringSink sink; | |
| 669 List<Object> seen; // TODO: that should be identity set. | |
| 670 | |
| 671 _JsonStringifier(this.sink) : seen = []; | |
| 672 | |
| 673 static String stringify(final object) { | |
| 674 StringBuffer output = new StringBuffer(); | |
| 675 _JsonStringifier stringifier = new _JsonStringifier(output); | |
| 676 stringifier.stringifyValue(object); | |
| 677 return output.toString(); | |
| 678 } | |
| 679 | |
| 680 static void printOn(final object, StringSink output) { | |
| 681 _JsonStringifier stringifier = new _JsonStringifier(output); | |
| 682 stringifier.stringifyValue(object); | |
| 683 } | |
| 684 | |
| 685 static String numberToString(num x) { | |
| 686 return x.toString(); | |
| 687 } | |
| 688 | |
| 689 // ('0' + x) or ('a' + x - 10) | |
| 690 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x; | |
| 691 | |
| 692 static void escape(StringSink sb, String s) { | |
| 693 final int length = s.length; | |
| 694 bool needsEscape = false; | |
| 695 final charCodes = new List<int>(); | |
| 696 for (int i = 0; i < length; i++) { | |
| 697 int charCode = s.codeUnitAt(i); | |
| 698 if (charCode < 32) { | |
| 699 needsEscape = true; | |
| 700 charCodes.add(JsonParser.BACKSLASH); | |
| 701 switch (charCode) { | |
| 702 case JsonParser.BACKSPACE: | |
| 703 charCodes.add(JsonParser.CHAR_b); | |
| 704 break; | |
| 705 case JsonParser.TAB: | |
| 706 charCodes.add(JsonParser.CHAR_t); | |
| 707 break; | |
| 708 case JsonParser.NEWLINE: | |
| 709 charCodes.add(JsonParser.CHAR_n); | |
| 710 break; | |
| 711 case JsonParser.FORM_FEED: | |
| 712 charCodes.add(JsonParser.CHAR_f); | |
| 713 break; | |
| 714 case JsonParser.CARRIAGE_RETURN: | |
| 715 charCodes.add(JsonParser.CHAR_r); | |
| 716 break; | |
| 717 default: | |
| 718 charCodes.add(JsonParser.CHAR_u); | |
| 719 charCodes.add(hexDigit((charCode >> 12) & 0xf)); | |
| 720 charCodes.add(hexDigit((charCode >> 8) & 0xf)); | |
| 721 charCodes.add(hexDigit((charCode >> 4) & 0xf)); | |
| 722 charCodes.add(hexDigit(charCode & 0xf)); | |
| 723 break; | |
| 724 } | |
| 725 } else if (charCode == JsonParser.QUOTE || | |
| 726 charCode == JsonParser.BACKSLASH) { | |
| 727 needsEscape = true; | |
| 728 charCodes.add(JsonParser.BACKSLASH); | |
| 729 charCodes.add(charCode); | |
| 730 } else { | |
| 731 charCodes.add(charCode); | |
| 732 } | |
| 733 } | |
| 734 sb.write(needsEscape ? new String.fromCharCodes(charCodes) : s); | |
| 735 } | |
| 736 | |
| 737 void checkCycle(final object) { | |
| 738 // TODO: use Iterables. | |
| 739 for (int i = 0; i < seen.length; i++) { | |
| 740 if (identical(seen[i], object)) { | |
| 741 throw new JsonCyclicError(object); | |
| 742 } | |
| 743 } | |
| 744 seen.add(object); | |
| 745 } | |
| 746 | |
| 747 void stringifyValue(final object) { | |
| 748 // Tries stringifying object directly. If it's not a simple value, List or | |
| 749 // Map, call toJson() to get a custom representation and try serializing | |
| 750 // that. | |
| 751 if (!stringifyJsonValue(object)) { | |
| 752 checkCycle(object); | |
| 753 try { | |
| 754 var customJson = object.toJson(); | |
| 755 if (!stringifyJsonValue(customJson)) { | |
| 756 throw new JsonUnsupportedObjectError(object); | |
| 757 } | |
| 758 seen.removeLast(); | |
| 759 } catch (e) { | |
| 760 throw new JsonUnsupportedObjectError(object, cause: e); | |
| 761 } | |
| 762 } | |
| 763 } | |
| 764 | |
| 765 /** | |
| 766 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value. | |
| 767 * | |
| 768 * Returns true if the value is one of these types, and false if not. | |
| 769 * If a value is both a [List] and a [Map], it's serialized as a [List]. | |
| 770 */ | |
| 771 bool stringifyJsonValue(final object) { | |
| 772 if (object is num) { | |
| 773 // TODO: use writeOn. | |
| 774 sink.write(numberToString(object)); | |
| 775 return true; | |
| 776 } else if (identical(object, true)) { | |
| 777 sink.write('true'); | |
| 778 return true; | |
| 779 } else if (identical(object, false)) { | |
| 780 sink.write('false'); | |
| 781 return true; | |
| 782 } else if (object == null) { | |
| 783 sink.write('null'); | |
| 784 return true; | |
| 785 } else if (object is String) { | |
| 786 sink.write('"'); | |
| 787 escape(sink, object); | |
| 788 sink.write('"'); | |
| 789 return true; | |
| 790 } else if (object is List) { | |
| 791 checkCycle(object); | |
| 792 List a = object; | |
| 793 sink.write('['); | |
| 794 if (a.length > 0) { | |
| 795 stringifyValue(a[0]); | |
| 796 // TODO: switch to Iterables. | |
| 797 for (int i = 1; i < a.length; i++) { | |
| 798 sink.write(','); | |
| 799 stringifyValue(a[i]); | |
| 800 } | |
| 801 } | |
| 802 sink.write(']'); | |
| 803 seen.removeLast(); | |
| 804 return true; | |
| 805 } else if (object is Map) { | |
| 806 checkCycle(object); | |
| 807 Map<String, Object> m = object; | |
| 808 sink.write('{'); | |
| 809 bool first = true; | |
| 810 m.forEach((String key, Object value) { | |
| 811 if (!first) { | |
| 812 sink.write(',"'); | |
| 813 } else { | |
| 814 sink.write('"'); | |
| 815 } | |
| 816 escape(sink, key); | |
| 817 sink.write('":'); | |
| 818 stringifyValue(value); | |
| 819 first = false; | |
| 820 }); | |
| 821 sink.write('}'); | |
| 822 seen.removeLast(); | |
| 823 return true; | |
| 824 } else { | |
| 825 return false; | |
| 826 } | |
| 827 } | |
| 828 } | |
| OLD | NEW |