| 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 |