OLD | NEW |
1 // Copyright (c) 2013, 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 part of dart.convert; | 5 part of dart.convert; |
6 | 6 |
7 /** | 7 /** |
8 * Error thrown by JSON serialization if an object cannot be serialized. | 8 * Error thrown by JSON serialization if an object cannot be serialized. |
9 * | 9 * |
10 * The [unsupportedObject] field holds that object that failed to be serialized. | 10 * The [unsupportedObject] field holds that object that failed to be serialized. |
11 * | 11 * |
12 * If an object isn't directly serializable, the serializer calls the 'toJson' | 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 | 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 | 14 * [cause] field. If the call returns an object that isn't directly |
15 * serializable, the [cause] is be null. | 15 * serializable, the [cause] is null. |
16 */ | 16 */ |
17 class JsonUnsupportedObjectError extends Error { | 17 class JsonUnsupportedObjectError extends Error { |
18 /** The object that could not be serialized. */ | 18 /** The object that could not be serialized. */ |
19 final unsupportedObject; | 19 final unsupportedObject; |
20 /** The exception thrown when trying to convert the object. */ | 20 /** The exception thrown when trying to convert the object. */ |
21 final cause; | 21 final cause; |
22 | 22 |
23 JsonUnsupportedObjectError(this.unsupportedObject, { this.cause }); | 23 JsonUnsupportedObjectError(this.unsupportedObject, { this.cause }); |
24 | 24 |
25 String toString() { | 25 String toString() { |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
58 */ | 58 */ |
59 const JsonCodec JSON = const JsonCodec(); | 59 const JsonCodec JSON = const JsonCodec(); |
60 | 60 |
61 typedef _Reviver(var key, var value); | 61 typedef _Reviver(var key, var value); |
62 typedef _ToEncodable(var o); | 62 typedef _ToEncodable(var o); |
63 | 63 |
64 | 64 |
65 /** | 65 /** |
66 * A [JsonCodec] encodes JSON objects to strings and decodes strings to | 66 * A [JsonCodec] encodes JSON objects to strings and decodes strings to |
67 * JSON objects. | 67 * JSON objects. |
| 68 * |
| 69 * Examples: |
| 70 * |
| 71 * var encoded = JSON.encode([1, 2, { "a": null }]); |
| 72 * var decoded = JSON.decode('["foo", { "bar": 499 }]'); |
68 */ | 73 */ |
69 class JsonCodec extends Codec<Object, String> { | 74 class JsonCodec extends Codec<Object, String> { |
70 final _Reviver _reviver; | 75 final _Reviver _reviver; |
71 final _ToEncodable _toEncodable; | 76 final _ToEncodable _toEncodable; |
72 | 77 |
73 /** | 78 /** |
74 * Creates a `JsonCodec` with the given reviver and encoding function. | 79 * Creates a `JsonCodec` with the given reviver and encoding function. |
75 * | 80 * |
76 * The [reviver] function is called during decoding. It is invoked | 81 * The [reviver] function is called during decoding. It is invoked once for |
77 * once for each object or list property that has been parsed. | 82 * each object or list property that has been parsed. |
78 * The `key` argument is either the | 83 * The `key` argument is either the integer list index for a list property, |
79 * integer list index for a list property, the string map key for object | 84 * the string map key for object properties, or `null` for the final result. |
80 * properties, or `null` for the final result. | |
81 * | 85 * |
82 * If [reviver] is omitted, it defaults to returning the value argument. | 86 * If [reviver] is omitted, it defaults to returning the value argument. |
83 * | 87 * |
84 * The [toEncodable] function is used during encoding. It is invoked for | 88 * The [toEncodable] function is used during encoding. It is invoked for |
85 * values that are not directly encodable to a JSON1toE | 89 * values that are not directly encodable to a string (a value that is not a |
86 * string (a value that is not a number, boolean, string, null, list or a map | 90 * number, boolean, string, null, list or a map with string keys). The |
87 * with string keys). The function must return an object that is directly | 91 * function must return an object that is directly encodable. The elements of |
88 * encodable. The elements of a returned list and values of a returned map | 92 * a returned list and values of a returned map do not need to be directly |
89 * do not need be directly encodable, and if they aren't, `toEncodable` will | 93 * encodable, and if they aren't, `toEncodable` will be used on them as well. |
90 * be used on them as well. | 94 * Please notice that it is possible to cause an infinite recursive regress |
91 * Please notice that it is possible to cause an infinite recursive | 95 * in this way, by effectively creating an infinite data structure through |
92 * regress in this way, by effectively creating an infinite data structure | 96 * repeated call to `toEncodable`. |
93 * through repeated call to `toEncodable`. | |
94 * | 97 * |
95 * If [toEncodable] is omitted, it defaults to a function that returns the | 98 * If [toEncodable] is omitted, it defaults to a function that returns the |
96 * result of calling `.toJson()` on the unencodable object. | 99 * result of calling `.toJson()` on the unencodable object. |
97 */ | 100 */ |
98 const JsonCodec({reviver(var key, var value), toEncodable(var object)}) | 101 const JsonCodec({reviver(var key, var value), toEncodable(var object)}) |
99 : _reviver = reviver, | 102 : _reviver = reviver, |
100 _toEncodable = toEncodable; | 103 _toEncodable = toEncodable; |
101 | 104 |
102 /** | 105 /** |
103 * Creates a `JsonCodec` with the given reviver. | 106 * Creates a `JsonCodec` with the given reviver. |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 | 152 |
150 JsonDecoder get decoder { | 153 JsonDecoder get decoder { |
151 if (_reviver == null) return const JsonDecoder(); | 154 if (_reviver == null) return const JsonDecoder(); |
152 return new JsonDecoder(_reviver); | 155 return new JsonDecoder(_reviver); |
153 } | 156 } |
154 } | 157 } |
155 | 158 |
156 /** | 159 /** |
157 * This class converts JSON objects to strings. | 160 * This class converts JSON objects to strings. |
158 */ | 161 */ |
159 class JsonEncoder extends Converter<Object, String> { | 162 class JsonEncoder extends ChunkedConverter<Object, String, Object, String> { |
160 /** | 163 /** |
161 * The string used for indention. | 164 * The string used for indention. |
162 * | 165 * |
163 * When generating multi-line output, this string is inserted once at the | 166 * When generating multi-line output, this string is inserted once at the |
164 * beginning of each indented line for each level of indentation. | 167 * beginning of each indented line for each level of indentation. |
165 * | 168 * |
166 * If `null`, the output is encoded as a single line. | 169 * If `null`, the output is encoded as a single line. |
167 */ | 170 */ |
168 final String indent; | 171 final String indent; |
169 | 172 |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
208 * the object. | 211 * the object. |
209 */ | 212 */ |
210 const JsonEncoder.withIndent(this.indent, | 213 const JsonEncoder.withIndent(this.indent, |
211 [Object toEncodable(Object nonSerializable)]) | 214 [Object toEncodable(Object nonSerializable)]) |
212 : this._toEncodable = toEncodable; | 215 : this._toEncodable = toEncodable; |
213 | 216 |
214 /** | 217 /** |
215 * Converts [object] to a JSON [String]. | 218 * Converts [object] to a JSON [String]. |
216 * | 219 * |
217 * Directly serializable values are [num], [String], [bool], and [Null], as | 220 * Directly serializable values are [num], [String], [bool], and [Null], as |
218 * well as some [List] and [Map] values. | 221 * well as some [List] and [Map] values. For [List], the elements must all be |
219 * For [List], the elements must all be serializable. | 222 * serializable. For [Map], the keys must be [String] and the values must be |
220 * For [Map], the keys must be [String] and the values must be serializable. | 223 * serializable. |
221 * | 224 * |
222 * If a value is any other type is attempted serialized, the conversion | 225 * If a value of any other type is attempted to be serialized, the |
223 * function provided in the constructor is invoked with the object as argument | 226 * `toEncodable` function provided in the constructor is called with the value |
224 * and the result, which must be a directly serializable value, | 227 * as argument. The result, which must be a directly serializable value, is |
225 * is serialized instead of the original value. | 228 * serialized instead of the original value. |
226 * | 229 * |
227 * If the conversion throws, or returns a value that is not directly | 230 * If the conversion throws, or returns a value that is not directly |
228 * serializable, a [JsonUnsupportedObjectError] exception is thrown. | 231 * serializable, a [JsonUnsupportedObjectError] exception is thrown. |
229 * If the call throws, the error is caught and stored in the | 232 * If the call throws, the error is caught and stored in the |
230 * [JsonUnsupportedObjectError]'s [:cause:] field. | 233 * [JsonUnsupportedObjectError]'s [:cause:] field. |
231 * | 234 * |
232 * If a [List] or [Map] contains a reference to itself, directly or through | 235 * If a [List] or [Map] contains a reference to itself, directly or through |
233 * other lists or maps, it cannot be serialized and a [JsonCyclicError] is | 236 * other lists or maps, it cannot be serialized and a [JsonCyclicError] is |
234 * thrown. | 237 * thrown. |
235 * | 238 * |
(...skipping 19 matching lines...) Expand all Loading... |
255 if (sink is! StringConversionSink) { | 258 if (sink is! StringConversionSink) { |
256 sink = new StringConversionSink.from(sink); | 259 sink = new StringConversionSink.from(sink); |
257 } else if (sink is _Utf8EncoderSink) { | 260 } else if (sink is _Utf8EncoderSink) { |
258 return new _JsonUtf8EncoderSink(sink._sink, _toEncodable, | 261 return new _JsonUtf8EncoderSink(sink._sink, _toEncodable, |
259 JsonUtf8Encoder._utf8Encode(indent), | 262 JsonUtf8Encoder._utf8Encode(indent), |
260 JsonUtf8Encoder.DEFAULT_BUFFER_SIZE); | 263 JsonUtf8Encoder.DEFAULT_BUFFER_SIZE); |
261 } | 264 } |
262 return new _JsonEncoderSink(sink, _toEncodable, indent); | 265 return new _JsonEncoderSink(sink, _toEncodable, indent); |
263 } | 266 } |
264 | 267 |
265 // Override the base-classes bind, to provide a better type. | 268 // Override the base class's bind, to provide a better type. |
266 Stream<String> bind(Stream<Object> stream) => super.bind(stream); | 269 Stream<String> bind(Stream<Object> stream) => super.bind(stream); |
267 | 270 |
268 Converter<Object, dynamic> fuse(Converter<String, dynamic> other) { | 271 Converter<Object, dynamic> fuse(Converter<String, dynamic> other) { |
269 if (other is Utf8Encoder) { | 272 if (other is Utf8Encoder) { |
270 return new JsonUtf8Encoder(indent, _toEncodable); | 273 return new JsonUtf8Encoder(indent, _toEncodable); |
271 } | 274 } |
272 return super.fuse(other); | 275 return super.fuse(other); |
273 } | 276 } |
274 } | 277 } |
275 | 278 |
276 /** | 279 /** |
277 * Encoder that encodes a single object as a UTF-8 encoded JSON string. | 280 * Encoder that encodes a single object as a UTF-8 encoded JSON string. |
278 * | 281 * |
279 * This encoder works equivalently to first converting the object to | 282 * This encoder works equivalently to first converting the object to |
280 * a JSON string, and then UTF-8 encoding the string, but without | 283 * a JSON string, and then UTF-8 encoding the string, but without |
281 * creating an intermediate string. | 284 * creating an intermediate string. |
282 */ | 285 */ |
283 class JsonUtf8Encoder extends Converter<Object, List<int>> { | 286 class JsonUtf8Encoder extends |
| 287 ChunkedConverter<Object, List<int>, Object, List<int>> { |
284 /** Default buffer size used by the JSON-to-UTF-8 encoder. */ | 288 /** Default buffer size used by the JSON-to-UTF-8 encoder. */ |
285 static const int DEFAULT_BUFFER_SIZE = 256; | 289 static const int DEFAULT_BUFFER_SIZE = 256; |
286 /** Indentation used in pretty-print mode, `null` if not pretty. */ | 290 /** Indentation used in pretty-print mode, `null` if not pretty. */ |
287 final List<int> _indent; | 291 final List<int> _indent; |
288 /** Function called with each un-encodable object encountered. */ | 292 /** Function called with each un-encodable object encountered. */ |
289 final Function _toEncodable; | 293 final Function _toEncodable; |
290 /** UTF-8 buffer size. */ | 294 /** UTF-8 buffer size. */ |
291 final int _bufferSize; | 295 final int _bufferSize; |
292 | 296 |
293 /** | 297 /** |
294 * Create converter. | 298 * Create converter. |
295 * | 299 * |
296 * If [indent] is non-`null`, the converter attempts to "pretty-print" the | 300 * If [indent] is non-`null`, the converter attempts to "pretty-print" the |
297 * JSON, and uses `indent` as the indentation. Otherwise the result has no | 301 * JSON, and uses `indent` as the indentation. Otherwise the result has no |
298 * whitespace outside of string literals. | 302 * whitespace outside of string literals. |
299 * If `indent` contains characters that are not valid JSON whitespace | 303 * If `indent` contains characters that are not valid JSON whitespace |
300 * characters, the result will not be valid JSON. JSON whitespace characters | 304 * characters, the result will not be valid JSON. JSON whitespace characters |
301 * are space (U+0020), tab (U+0009), line feed (U+000a) and carriage return | 305 * are space (U+0020), tab (U+0009), line feed (U+000a) and carriage return |
302 * (U+000d) (ECMA 404). | 306 * (U+000d) ([ECMA |
| 307 * 404](http://www.ecma-international.org/publications/standards/Ecma-404.htm)
). |
303 * | 308 * |
304 * The [bufferSize] is the size of the internal buffers used to collect | 309 * The [bufferSize] is the size of the internal buffers used to collect |
305 * UTF-8 code units. | 310 * UTF-8 code units. |
306 * If using [startChunkedConversion], it will be the size of the chunks. | 311 * If using [startChunkedConversion], it will be the size of the chunks. |
307 * | 312 * |
308 * The JSON encoder handles numbers, strings, booleans, null, lists and | 313 * The JSON encoder handles numbers, strings, booleans, null, lists and maps |
309 * maps directly. | 314 * directly. |
310 * | 315 * |
311 * Any other object is attempted converted by [toEncodable] to an | 316 * Any other object is attempted converted by [toEncodable] to an object that |
312 * object that is of one of the convertible types. | 317 * is of one of the convertible types. |
313 * | 318 * |
314 * If [toEncodable] is omitted, it defaults to calling `.toJson()` on | 319 * If [toEncodable] is omitted, it defaults to calling `.toJson()` on the |
315 * the object. | 320 * object. |
316 */ | 321 */ |
317 JsonUtf8Encoder([String indent, | 322 JsonUtf8Encoder([String indent, |
318 toEncodable(Object object), | 323 toEncodable(Object object), |
319 int bufferSize = DEFAULT_BUFFER_SIZE]) | 324 int bufferSize = DEFAULT_BUFFER_SIZE]) |
320 : _indent = _utf8Encode(indent), | 325 : _indent = _utf8Encode(indent), |
321 _toEncodable = toEncodable, | 326 _toEncodable = toEncodable, |
322 _bufferSize = bufferSize; | 327 _bufferSize = bufferSize; |
323 | 328 |
324 static List<int> _utf8Encode(String string) { | 329 static List<int> _utf8Encode(String string) { |
325 if (string == null) return null; | 330 if (string == null) return null; |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
379 ByteConversionSink byteSink; | 384 ByteConversionSink byteSink; |
380 if (sink is ByteConversionSink) { | 385 if (sink is ByteConversionSink) { |
381 byteSink = sink; | 386 byteSink = sink; |
382 } else { | 387 } else { |
383 byteSink = new ByteConversionSink.from(sink); | 388 byteSink = new ByteConversionSink.from(sink); |
384 } | 389 } |
385 return new _JsonUtf8EncoderSink(byteSink, _toEncodable, | 390 return new _JsonUtf8EncoderSink(byteSink, _toEncodable, |
386 _indent, _bufferSize); | 391 _indent, _bufferSize); |
387 } | 392 } |
388 | 393 |
389 // Override the base-classes bind, to provide a better type. | 394 // Override the base class's bind, to provide a better type. |
390 Stream<List<int>> bind(Stream<Object> stream) { | 395 Stream<List<int>> bind(Stream<Object> stream) { |
391 return super.bind(stream); | 396 return super.bind(stream); |
392 } | 397 } |
393 | 398 |
394 Converter<Object, dynamic> fuse(Converter<List<int>, dynamic> other) { | 399 Converter<Object, dynamic> fuse(Converter<List<int>, dynamic> other) { |
395 return super.fuse(other); | 400 return super.fuse(other); |
396 } | 401 } |
397 } | 402 } |
398 | 403 |
399 /** | 404 /** |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
462 if (!_isDone) { | 467 if (!_isDone) { |
463 _isDone = true; | 468 _isDone = true; |
464 _sink.close(); | 469 _sink.close(); |
465 } | 470 } |
466 } | 471 } |
467 } | 472 } |
468 | 473 |
469 /** | 474 /** |
470 * This class parses JSON strings and builds the corresponding objects. | 475 * This class parses JSON strings and builds the corresponding objects. |
471 */ | 476 */ |
472 class JsonDecoder extends Converter<String, Object> { | 477 class JsonDecoder extends ChunkedConverter<String, Object, String, Object> { |
473 final _Reviver _reviver; | 478 final _Reviver _reviver; |
474 /** | 479 /** |
475 * Constructs a new JsonDecoder. | 480 * Constructs a new JsonDecoder. |
476 * | 481 * |
477 * The [reviver] may be `null`. | 482 * The [reviver] may be `null`. |
478 */ | 483 */ |
479 const JsonDecoder([reviver(var key, var value)]) : this._reviver = reviver; | 484 const JsonDecoder([reviver(var key, var value)]) : this._reviver = reviver; |
480 | 485 |
481 /** | 486 /** |
482 * Converts the given JSON-string [input] to its corresponding object. | 487 * Converts the given JSON-string [input] to its corresponding object. |
483 * | 488 * |
484 * Parsed JSON values are of the types [num], [String], [bool], [Null], | 489 * Parsed JSON values are of the types [num], [String], [bool], [Null], |
485 * [List]s of parsed JSON values or [Map]s from [String] to parsed | 490 * [List]s of parsed JSON values or [Map]s from [String] to parsed JSON |
486 * JSON values. | 491 * values. |
487 * | 492 * |
488 * If `this` was initialized with a reviver, then the parsing operation | 493 * If `this` was initialized with a reviver, then the parsing operation |
489 * invokes the reviver on every object or list property that has been parsed. | 494 * invokes the reviver on every object or list property that has been parsed. |
490 * The arguments are the property name ([String]) or list index ([int]), and | 495 * The arguments are the property name ([String]) or list index ([int]), and |
491 * the value is the parsed value. The return value of the reviver is used as | 496 * the value is the parsed value. The return value of the reviver is used as |
492 * the value of that property instead the parsed value. | 497 * the value of that property instead the parsed value. |
493 * | 498 * |
494 * Throws [FormatException] if the input is not valid JSON text. | 499 * Throws [FormatException] if the input is not valid JSON text. |
495 */ | 500 */ |
496 dynamic convert(String input) => _parseJson(input, _reviver); | 501 dynamic convert(String input) => _parseJson(input, _reviver); |
497 | 502 |
498 /** | 503 /** |
499 * Starts a conversion from a chunked JSON string to its corresponding | 504 * Starts a conversion from a chunked JSON string to its corresponding object. |
500 * object. | |
501 * | 505 * |
502 * The output [sink] receives exactly one decoded element through `add`. | 506 * The output [sink] receives exactly one decoded element through `add`. |
503 */ | 507 */ |
504 external StringConversionSink startChunkedConversion(Sink<Object> sink); | 508 external StringConversionSink startChunkedConversion(Sink<Object> sink); |
505 | 509 |
506 // Override the base-classes bind, to provide a better type. | 510 // Override the base class's bind, to provide a better type. |
507 Stream<Object> bind(Stream<String> stream) => super.bind(stream); | 511 Stream<Object> bind(Stream<String> stream) => super.bind(stream); |
508 } | 512 } |
509 | 513 |
510 // Internal optimized JSON parsing implementation. | 514 // Internal optimized JSON parsing implementation. |
511 external _parseJson(String source, reviver(key, value)); | 515 external _parseJson(String source, reviver(key, value)); |
512 | 516 |
513 | 517 |
514 // Implementation of encoder/stringifier. | 518 // Implementation of encoder/stringifier. |
515 | 519 |
516 Object _defaultToEncodable(object) => object.toJson(); | 520 Object _defaultToEncodable(object) => object.toJson(); |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
606 if (offset == 0) { | 610 if (offset == 0) { |
607 writeString(s); | 611 writeString(s); |
608 } else if (offset < length) { | 612 } else if (offset < length) { |
609 writeStringSlice(s, offset, length); | 613 writeStringSlice(s, offset, length); |
610 } | 614 } |
611 } | 615 } |
612 | 616 |
613 /** | 617 /** |
614 * Check if an encountered object is already being traversed. | 618 * Check if an encountered object is already being traversed. |
615 * | 619 * |
616 * Records the object if it isn't already seen. | 620 * Records the object if it isn't already seen. Should have a matching call to |
617 * Should have a matching call to [_removeSeen] when the object | 621 * [_removeSeen] when the object is no longer being traversed. |
618 * is no longer being traversed. | |
619 */ | 622 */ |
620 void _checkCycle(object) { | 623 void _checkCycle(object) { |
621 for (int i = 0; i < _seen.length; i++) { | 624 for (int i = 0; i < _seen.length; i++) { |
622 if (identical(object, _seen[i])) { | 625 if (identical(object, _seen[i])) { |
623 throw new JsonCyclicError(object); | 626 throw new JsonCyclicError(object); |
624 } | 627 } |
625 } | 628 } |
626 _seen.add(object); | 629 _seen.add(object); |
627 } | 630 } |
628 | 631 |
629 /** | 632 /** |
630 * Removes object from the list of currently traversed objects. | 633 * Remove [object] from the list of currently traversed objects. |
631 * | 634 * |
632 * Should be called in the opposite order of the matching [_checkCycle] | 635 * Should be called in the opposite order of the matching [_checkCycle] |
633 * calls. | 636 * calls. |
634 */ | 637 */ |
635 void _removeSeen(object) { | 638 void _removeSeen(object) { |
636 assert(!_seen.isEmpty); | 639 assert(!_seen.isEmpty); |
637 assert(identical(_seen.last, object)); | 640 assert(identical(_seen.last, object)); |
638 _seen.removeLast(); | 641 _seen.removeLast(); |
639 } | 642 } |
640 | 643 |
641 /** | 644 /** |
642 * Writes an object. | 645 * Write an object. |
643 * | 646 * |
644 * If the object isn't directly encodable, the [_toEncodable] function | 647 * If [object] isn't directly encodable, the [_toEncodable] function gets one |
645 * gets one chance to return a replacement which is encodable. | 648 * chance to return a replacement which is encodable. |
646 */ | 649 */ |
647 void writeObject(object) { | 650 void writeObject(object) { |
648 // Tries stringifying object directly. If it's not a simple value, List or | 651 // Tries stringifying object directly. If it's not a simple value, List or |
649 // Map, call toJson() to get a custom representation and try serializing | 652 // Map, call toJson() to get a custom representation and try serializing |
650 // that. | 653 // that. |
651 if (writeJsonValue(object)) return; | 654 if (writeJsonValue(object)) return; |
652 _checkCycle(object); | 655 _checkCycle(object); |
653 try { | 656 try { |
654 var customJson = _toEncodable(object); | 657 var customJson = _toEncodable(object); |
655 if (!writeJsonValue(customJson)) { | 658 if (!writeJsonValue(customJson)) { |
656 throw new JsonUnsupportedObjectError(object); | 659 throw new JsonUnsupportedObjectError(object); |
657 } | 660 } |
658 _removeSeen(object); | 661 _removeSeen(object); |
659 } catch (e) { | 662 } catch (e) { |
660 throw new JsonUnsupportedObjectError(object, cause: e); | 663 throw new JsonUnsupportedObjectError(object, cause: e); |
661 } | 664 } |
662 } | 665 } |
663 | 666 |
664 /** | 667 /** |
665 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value. | 668 * Serialize a [num], [String], [bool], [Null], [List] or [Map] value. |
666 * | 669 * |
667 * Returns true if the value is one of these types, and false if not. | 670 * Returns true if the value is one of these types, and false if not. |
668 * If a value is both a [List] and a [Map], it's serialized as a [List]. | 671 * If a value is both a [List] and a [Map], it's serialized as a [List]. |
669 */ | 672 */ |
670 bool writeJsonValue(object) { | 673 bool writeJsonValue(object) { |
671 if (object is num) { | 674 if (object is num) { |
672 if (!object.isFinite) return false; | 675 if (!object.isFinite) return false; |
673 writeNumber(object); | 676 writeNumber(object); |
674 return true; | 677 return true; |
675 } else if (identical(object, true)) { | 678 } else if (identical(object, true)) { |
(...skipping 10 matching lines...) Expand all Loading... |
686 writeStringContent(object); | 689 writeStringContent(object); |
687 writeString('"'); | 690 writeString('"'); |
688 return true; | 691 return true; |
689 } else if (object is List) { | 692 } else if (object is List) { |
690 _checkCycle(object); | 693 _checkCycle(object); |
691 writeList(object); | 694 writeList(object); |
692 _removeSeen(object); | 695 _removeSeen(object); |
693 return true; | 696 return true; |
694 } else if (object is Map) { | 697 } else if (object is Map) { |
695 _checkCycle(object); | 698 _checkCycle(object); |
696 writeMap(object); | 699 // writeMap can fail if keys are not all strings. |
| 700 var success = writeMap(object); |
697 _removeSeen(object); | 701 _removeSeen(object); |
698 return true; | 702 return success; |
699 } else { | 703 } else { |
700 return false; | 704 return false; |
701 } | 705 } |
702 } | 706 } |
703 | 707 |
704 /** Serializes a [List]. */ | 708 /** Serialize a [List]. */ |
705 void writeList(List list) { | 709 void writeList(List list) { |
706 writeString('['); | 710 writeString('['); |
707 if (list.length > 0) { | 711 if (list.length > 0) { |
708 writeObject(list[0]); | 712 writeObject(list[0]); |
709 for (int i = 1; i < list.length; i++) { | 713 for (int i = 1; i < list.length; i++) { |
710 writeString(','); | 714 writeString(','); |
711 writeObject(list[i]); | 715 writeObject(list[i]); |
712 } | 716 } |
713 } | 717 } |
714 writeString(']'); | 718 writeString(']'); |
715 } | 719 } |
716 | 720 |
717 /** Serializes a [Map]. */ | 721 /** Serialize a [Map]. */ |
718 void writeMap(Map<String, Object> map) { | 722 bool writeMap(Map<String, Object> map) { |
| 723 if (map.isEmpty) { |
| 724 writeString("{}"); |
| 725 return true; |
| 726 } |
| 727 List keyValueList = new List(map.length * 2); |
| 728 int i = 0; |
| 729 bool allStringKeys = true; |
| 730 map.forEach((key, value) { |
| 731 if (key is! String) { |
| 732 allStringKeys = false; |
| 733 } |
| 734 keyValueList[i++] = key; |
| 735 keyValueList[i++] = value; |
| 736 }); |
| 737 if (!allStringKeys) return false; |
719 writeString('{'); | 738 writeString('{'); |
720 String separator = '"'; | 739 String separator = '"'; |
721 map.forEach((String key, value) { | 740 for (int i = 0; i < keyValueList.length; i += 2) { |
722 writeString(separator); | 741 writeString(separator); |
723 separator = ',"'; | 742 separator = ',"'; |
724 writeStringContent(key); | 743 writeStringContent(keyValueList[i]); |
725 writeString('":'); | 744 writeString('":'); |
726 writeObject(value); | 745 writeObject(keyValueList[i + 1]); |
727 }); | 746 } |
728 writeString('}'); | 747 writeString('}'); |
| 748 return true; |
729 } | 749 } |
730 } | 750 } |
731 | 751 |
732 /** | 752 /** |
733 * A modification of [_JsonStringifier] which indents the contents of [List] and | 753 * A modification of [_JsonStringifier] which indents the contents of [List] and |
734 * [Map] objects using the specified indent value. | 754 * [Map] objects using the specified indent value. |
735 * | 755 * |
736 * Subclasses should implement [writeIndentation]. | 756 * Subclasses should implement [writeIndentation]. |
737 */ | 757 */ |
738 abstract class _JsonPrettyPrintMixin implements _JsonStringifier { | 758 abstract class _JsonPrettyPrintMixin implements _JsonStringifier { |
(...skipping 17 matching lines...) Expand all Loading... |
756 writeIndentation(_indentLevel); | 776 writeIndentation(_indentLevel); |
757 writeObject(list[i]); | 777 writeObject(list[i]); |
758 } | 778 } |
759 writeString('\n'); | 779 writeString('\n'); |
760 _indentLevel--; | 780 _indentLevel--; |
761 writeIndentation(_indentLevel); | 781 writeIndentation(_indentLevel); |
762 writeString(']'); | 782 writeString(']'); |
763 } | 783 } |
764 } | 784 } |
765 | 785 |
766 void writeMap(Map map) { | 786 bool writeMap(Map map) { |
767 if (map.isEmpty) { | 787 if (map.isEmpty) { |
768 writeString('{}'); | 788 writeString("{}"); |
769 } else { | 789 return true; |
770 writeString('{\n'); | 790 } |
771 _indentLevel++; | 791 List keyValueList = new List(map.length * 2); |
772 bool first = true; | 792 int i = 0; |
773 map.forEach((String key, Object value) { | 793 bool allStringKeys = true; |
774 if (!first) { | 794 map.forEach((key, value) { |
775 writeString(",\n"); | 795 if (key is! String) { |
776 } | 796 allStringKeys = false; |
777 writeIndentation(_indentLevel); | 797 } |
778 writeString('"'); | 798 keyValueList[i++] = key; |
779 writeStringContent(key); | 799 keyValueList[i++] = value; |
780 writeString('": '); | 800 }); |
781 writeObject(value); | 801 if (!allStringKeys) return false; |
782 first = false; | 802 writeString('{\n'); |
783 }); | 803 _indentLevel++; |
784 writeString('\n'); | 804 String separator = ""; |
785 _indentLevel--; | 805 for (int i = 0; i < keyValueList.length; i += 2) { |
| 806 writeString(separator); |
| 807 separator = ",\n"; |
786 writeIndentation(_indentLevel); | 808 writeIndentation(_indentLevel); |
787 writeString('}'); | 809 writeString('"'); |
| 810 writeStringContent(keyValueList[i]); |
| 811 writeString('": '); |
| 812 writeObject(keyValueList[i + 1]); |
788 } | 813 } |
| 814 writeString('\n'); |
| 815 _indentLevel--; |
| 816 writeIndentation(_indentLevel); |
| 817 writeString('}'); |
| 818 return true; |
789 } | 819 } |
790 } | 820 } |
791 | 821 |
792 /** | 822 /** |
793 * A specialziation of [_JsonStringifier] that writes its JSON to a string. | 823 * A specialziation of [_JsonStringifier] that writes its JSON to a string. |
794 */ | 824 */ |
795 class _JsonStringStringifier extends _JsonStringifier { | 825 class _JsonStringStringifier extends _JsonStringifier { |
796 final StringSink _sink; | 826 final StringSink _sink; |
797 | 827 |
798 _JsonStringStringifier(this._sink, _toEncodable) : super(_toEncodable); | 828 _JsonStringStringifier(this._sink, _toEncodable) : super(_toEncodable); |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
866 */ | 896 */ |
867 class _JsonUtf8Stringifier extends _JsonStringifier { | 897 class _JsonUtf8Stringifier extends _JsonStringifier { |
868 final int bufferSize; | 898 final int bufferSize; |
869 final Function addChunk; | 899 final Function addChunk; |
870 Uint8List buffer; | 900 Uint8List buffer; |
871 int index = 0; | 901 int index = 0; |
872 | 902 |
873 _JsonUtf8Stringifier(toEncodable, int bufferSize, this.addChunk) | 903 _JsonUtf8Stringifier(toEncodable, int bufferSize, this.addChunk) |
874 : this.bufferSize = bufferSize, | 904 : this.bufferSize = bufferSize, |
875 buffer = new Uint8List(bufferSize), | 905 buffer = new Uint8List(bufferSize), |
876 super(toEncodable) | 906 super(toEncodable); |
877 ; | |
878 | 907 |
879 /** | 908 /** |
880 * Convert [object] to UTF-8 encoded JSON. | 909 * Convert [object] to UTF-8 encoded JSON. |
881 * | 910 * |
882 * Calls [addChunk] with slices of UTF-8 code units. | 911 * Calls [addChunk] with slices of UTF-8 code units. |
883 * These will typically have size [bufferSize], but may be shorter. | 912 * These will typically have size [bufferSize], but may be shorter. |
884 * The buffers are not reused, so the [addChunk] call may keep and reuse | 913 * The buffers are not reused, so the [addChunk] call may keep and reuse the |
885 * the chunks. | 914 * chunks. |
886 * | 915 * |
887 * If [indent] is non-`null`, the result will be "pretty-printed" with | 916 * If [indent] is non-`null`, the result will be "pretty-printed" with extra |
888 * extra newlines and indentation, using [indent] as the indentation. | 917 * newlines and indentation, using [indent] as the indentation. |
889 */ | 918 */ |
890 static void stringify(Object object, | 919 static void stringify(Object object, |
891 List<int> indent, | 920 List<int> indent, |
892 toEncodableFunction(Object o), | 921 toEncodableFunction(Object o), |
893 int bufferSize, | 922 int bufferSize, |
894 void addChunk(Uint8List chunk, int start, int end)) { | 923 void addChunk(Uint8List chunk, int start, int end)) { |
895 _JsonUtf8Stringifier stringifier; | 924 _JsonUtf8Stringifier stringifier; |
896 if (indent != null) { | 925 if (indent != null) { |
897 stringifier = new _JsonUtf8StringifierPretty(toEncodableFunction, indent, | 926 stringifier = new _JsonUtf8StringifierPretty(toEncodableFunction, indent, |
898 bufferSize, addChunk); | 927 bufferSize, addChunk); |
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1030 buffer.setRange(index, end, indent); | 1059 buffer.setRange(index, end, indent); |
1031 index = end; | 1060 index = end; |
1032 } else { | 1061 } else { |
1033 for (int i = 0; i < indentLength; i++) { | 1062 for (int i = 0; i < indentLength; i++) { |
1034 writeByte(indent[i]); | 1063 writeByte(indent[i]); |
1035 } | 1064 } |
1036 } | 1065 } |
1037 } | 1066 } |
1038 } | 1067 } |
1039 } | 1068 } |
OLD | NEW |