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 |