| OLD | NEW | 
|---|
|  | (Empty) | 
| 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 |  | 
| 3 // BSD-style license that can be found in the LICENSE file. |  | 
| 4 |  | 
| 5 part of dart.convert; |  | 
| 6 |  | 
| 7 /** |  | 
| 8  * Error thrown by JSON serialization if an object cannot be serialized. |  | 
| 9  * |  | 
| 10  * The [unsupportedObject] field holds that object that failed to be serialized. |  | 
| 11  * |  | 
| 12  * If an object isn't directly serializable, the serializer calls the `toJson` |  | 
| 13  * method on the object. If that call fails, the error will be stored in the |  | 
| 14  * [cause] field. If the call returns an object that isn't directly |  | 
| 15  * serializable, the [cause] is null. |  | 
| 16  */ |  | 
| 17 class JsonUnsupportedObjectError extends Error { |  | 
| 18   /** The object that could not be serialized. */ |  | 
| 19   final unsupportedObject; |  | 
| 20   /** The exception thrown when trying to convert the object. */ |  | 
| 21   final cause; |  | 
| 22 |  | 
| 23   JsonUnsupportedObjectError(this.unsupportedObject, { this.cause }); |  | 
| 24 |  | 
| 25   String toString() { |  | 
| 26     if (cause != null) { |  | 
| 27       return "Converting object to an encodable object failed."; |  | 
| 28     } else { |  | 
| 29       return "Converting object did not return an encodable object."; |  | 
| 30     } |  | 
| 31   } |  | 
| 32 } |  | 
| 33 |  | 
| 34 |  | 
| 35 /** |  | 
| 36  * Reports that an object could not be stringified due to cyclic references. |  | 
| 37  * |  | 
| 38  * An object that references itself cannot be serialized by [stringify]. |  | 
| 39  * When the cycle is detected, a [JsonCyclicError] is thrown. |  | 
| 40  */ |  | 
| 41 class JsonCyclicError extends JsonUnsupportedObjectError { |  | 
| 42   /** The first object that was detected as part of a cycle. */ |  | 
| 43   JsonCyclicError(Object object): super(object); |  | 
| 44   String toString() => "Cyclic error in JSON stringify"; |  | 
| 45 } |  | 
| 46 |  | 
| 47 |  | 
| 48 /** |  | 
| 49  * An instance of the default implementation of the [JsonCodec]. |  | 
| 50  * |  | 
| 51  * This instance provides a convenient access to the most common JSON |  | 
| 52  * use cases. |  | 
| 53  * |  | 
| 54  * Examples: |  | 
| 55  * |  | 
| 56  *     var encoded = JSON.encode([1, 2, { "a": null }]); |  | 
| 57  *     var decoded = JSON.decode('["foo", { "bar": 499 }]'); |  | 
| 58  */ |  | 
| 59 const JsonCodec JSON = const JsonCodec(); |  | 
| 60 |  | 
| 61 typedef _Reviver(var key, var value); |  | 
| 62 typedef _ToEncodable(var o); |  | 
| 63 |  | 
| 64 |  | 
| 65 /** |  | 
| 66  * A [JsonCodec] encodes JSON objects to strings and decodes strings to |  | 
| 67  * JSON objects. |  | 
| 68  * |  | 
| 69  * Examples: |  | 
| 70  * |  | 
| 71  *     var encoded = JSON.encode([1, 2, { "a": null }]); |  | 
| 72  *     var decoded = JSON.decode('["foo", { "bar": 499 }]'); |  | 
| 73  */ |  | 
| 74 class JsonCodec extends Codec<Object, String> { |  | 
| 75   final _Reviver _reviver; |  | 
| 76   final _ToEncodable _toEncodable; |  | 
| 77 |  | 
| 78   /** |  | 
| 79    * Creates a `JsonCodec` with the given reviver and encoding function. |  | 
| 80    * |  | 
| 81    * The [reviver] function is called during decoding. It is invoked once for |  | 
| 82    * each object or list property that has been parsed. |  | 
| 83    * The `key` argument is either the integer list index for a list property, |  | 
| 84    * the string map key for object properties, or `null` for the final result. |  | 
| 85    * |  | 
| 86    * If [reviver] is omitted, it defaults to returning the value argument. |  | 
| 87    * |  | 
| 88    * The [toEncodable] function is used during encoding. It is invoked for |  | 
| 89    * values that are not directly encodable to a string (a value that is not a |  | 
| 90    * number, boolean, string, null, list or a map with string keys). The |  | 
| 91    * function must return an object that is directly encodable. The elements of |  | 
| 92    * a returned list and values of a returned map do not need to be directly |  | 
| 93    * encodable, and if they aren't, `toEncodable` will be used on them as well. |  | 
| 94    * Please notice that it is possible to cause an infinite recursive regress |  | 
| 95    * in this way, by effectively creating an infinite data structure through |  | 
| 96    * repeated call to `toEncodable`. |  | 
| 97    * |  | 
| 98    * If [toEncodable] is omitted, it defaults to a function that returns the |  | 
| 99    * result of calling `.toJson()` on the unencodable object. |  | 
| 100    */ |  | 
| 101   const JsonCodec({reviver(var key, var value), toEncodable(var object)}) |  | 
| 102       : _reviver = reviver, |  | 
| 103         _toEncodable = toEncodable; |  | 
| 104 |  | 
| 105   /** |  | 
| 106    * Creates a `JsonCodec` with the given reviver. |  | 
| 107    * |  | 
| 108    * The [reviver] function is called once for each object or list property |  | 
| 109    * that has been parsed during decoding. The `key` argument is either the |  | 
| 110    * integer list index for a list property, the string map key for object |  | 
| 111    * properties, or `null` for the final result. |  | 
| 112    */ |  | 
| 113   JsonCodec.withReviver(reviver(var key, var value)) : this(reviver: reviver); |  | 
| 114 |  | 
| 115   /** |  | 
| 116    * Parses the string and returns the resulting Json object. |  | 
| 117    * |  | 
| 118    * The optional [reviver] function is called once for each object or list |  | 
| 119    * property that has been parsed during decoding. The `key` argument is either |  | 
| 120    * the integer list index for a list property, the string map key for object |  | 
| 121    * properties, or `null` for the final result. |  | 
| 122    * |  | 
| 123    * The default [reviver] (when not provided) is the identity function. |  | 
| 124    */ |  | 
| 125   dynamic decode(String source, {reviver(var key, var value)}) { |  | 
| 126     if (reviver == null) reviver = _reviver; |  | 
| 127     if (reviver == null) return decoder.convert(source); |  | 
| 128     return new JsonDecoder(reviver).convert(source); |  | 
| 129   } |  | 
| 130 |  | 
| 131   /** |  | 
| 132    * Converts [value] to a JSON string. |  | 
| 133    * |  | 
| 134    * If value contains objects that are not directly encodable to a JSON |  | 
| 135    * string (a value that is not a number, boolean, string, null, list or a map |  | 
| 136    * with string keys), the [toEncodable] function is used to convert it to an |  | 
| 137    * object that must be directly encodable. |  | 
| 138    * |  | 
| 139    * If [toEncodable] is omitted, it defaults to a function that returns the |  | 
| 140    * result of calling `.toJson()` on the unencodable object. |  | 
| 141    */ |  | 
| 142   String encode(Object value, {toEncodable(object)}) { |  | 
| 143     if (toEncodable == null) toEncodable = _toEncodable; |  | 
| 144     if (toEncodable == null) return encoder.convert(value); |  | 
| 145     return new JsonEncoder(toEncodable).convert(value); |  | 
| 146   } |  | 
| 147 |  | 
| 148   JsonEncoder get encoder { |  | 
| 149     if (_toEncodable == null) return const JsonEncoder(); |  | 
| 150     return new JsonEncoder(_toEncodable); |  | 
| 151   } |  | 
| 152 |  | 
| 153   JsonDecoder get decoder { |  | 
| 154     if (_reviver == null) return const JsonDecoder(); |  | 
| 155     return new JsonDecoder(_reviver); |  | 
| 156   } |  | 
| 157 } |  | 
| 158 |  | 
| 159 /** |  | 
| 160  * This class converts JSON objects to strings. |  | 
| 161  */ |  | 
| 162 class JsonEncoder extends Converter<Object, String> { |  | 
| 163   /** |  | 
| 164    * The string used for indention. |  | 
| 165    * |  | 
| 166    * When generating multi-line output, this string is inserted once at the |  | 
| 167    * beginning of each indented line for each level of indentation. |  | 
| 168    * |  | 
| 169    * If `null`, the output is encoded as a single line. |  | 
| 170    */ |  | 
| 171   final String indent; |  | 
| 172 |  | 
| 173   /** |  | 
| 174    * Function called on non-encodable objects to return a replacement |  | 
| 175    * encodable object that will be encoded in the orignal's place. |  | 
| 176    */ |  | 
| 177   final _ToEncodable _toEncodable; |  | 
| 178 |  | 
| 179   /** |  | 
| 180    * Creates a JSON encoder. |  | 
| 181    * |  | 
| 182    * The JSON encoder handles numbers, strings, booleans, null, lists and |  | 
| 183    * maps directly. |  | 
| 184    * |  | 
| 185    * Any other object is attempted converted by [toEncodable] to an |  | 
| 186    * object that is of one of the convertible types. |  | 
| 187    * |  | 
| 188    * If [toEncodable] is omitted, it defaults to calling `.toJson()` on |  | 
| 189    * the object. |  | 
| 190    */ |  | 
| 191   const JsonEncoder([toEncodable(nonSerializable)]) |  | 
| 192       : this.indent = null, |  | 
| 193         this._toEncodable = toEncodable; |  | 
| 194 |  | 
| 195   /** |  | 
| 196    * Creates a JSON encoder that creates multi-line JSON. |  | 
| 197    * |  | 
| 198    * The encoding of elements of lists and maps are indented and put on separate |  | 
| 199    * lines. The [indent] string is prepended to these elements, once for each |  | 
| 200    * level of indentation. |  | 
| 201    * |  | 
| 202    * If [indent] is `null`, the output is encoded as a single line. |  | 
| 203    * |  | 
| 204    * The JSON encoder handles numbers, strings, booleans, null, lists and |  | 
| 205    * maps directly. |  | 
| 206    * |  | 
| 207    * Any other object is attempted converted by [toEncodable] to an |  | 
| 208    * object that is of one of the convertible types. |  | 
| 209    * |  | 
| 210    * If [toEncodable] is omitted, it defaults to calling `.toJson()` on |  | 
| 211    * the object. |  | 
| 212    */ |  | 
| 213   const JsonEncoder.withIndent(this.indent, [toEncodable(nonSerializable)]) |  | 
| 214       : this._toEncodable = toEncodable; |  | 
| 215 |  | 
| 216   /** |  | 
| 217    * Converts [object] to a JSON [String]. |  | 
| 218    * |  | 
| 219    * Directly serializable values are [num], [String], [bool], and [Null], as |  | 
| 220    * well as some [List] and [Map] values. For [List], the elements must all be |  | 
| 221    * serializable. For [Map], the keys must be [String] and the values must be |  | 
| 222    * serializable. |  | 
| 223    * |  | 
| 224    * If a value of any other type is attempted to be serialized, the |  | 
| 225    * `toEncodable` function provided in the constructor is called with the value |  | 
| 226    * as argument. The result, which must be a directly serializable value, is |  | 
| 227    * serialized instead of the original value. |  | 
| 228    * |  | 
| 229    * If the conversion throws, or returns a value that is not directly |  | 
| 230    * serializable, a [JsonUnsupportedObjectError] exception is thrown. |  | 
| 231    * If the call throws, the error is caught and stored in the |  | 
| 232    * [JsonUnsupportedObjectError]'s [:cause:] field. |  | 
| 233    * |  | 
| 234    * If a [List] or [Map] contains a reference to itself, directly or through |  | 
| 235    * other lists or maps, it cannot be serialized and a [JsonCyclicError] is |  | 
| 236    * thrown. |  | 
| 237    * |  | 
| 238    * [object] should not change during serialization. |  | 
| 239    * |  | 
| 240    * If an object is serialized more than once, [convert] may cache the text |  | 
| 241    * for it. In other words, if the content of an object changes after it is |  | 
| 242    * first serialized, the new values may not be reflected in the result. |  | 
| 243    */ |  | 
| 244   String convert(Object object) => |  | 
| 245       _JsonStringStringifier.stringify(object, _toEncodable, indent); |  | 
| 246 |  | 
| 247   /** |  | 
| 248    * Starts a chunked conversion. |  | 
| 249    * |  | 
| 250    * The converter works more efficiently if the given [sink] is a |  | 
| 251    * [StringConversionSink]. |  | 
| 252    * |  | 
| 253    * Returns a chunked-conversion sink that accepts at most one object. It is |  | 
| 254    * an error to invoke `add` more than once on the returned sink. |  | 
| 255    */ |  | 
| 256   ChunkedConversionSink<Object> startChunkedConversion(Sink<String> sink) { |  | 
| 257     if (sink is! StringConversionSink) { |  | 
| 258       sink = new StringConversionSink.from(sink); |  | 
| 259     } else if (sink is _Utf8EncoderSink) { |  | 
| 260       return new _JsonUtf8EncoderSink(sink._sink, _toEncodable, |  | 
| 261                                       JsonUtf8Encoder._utf8Encode(indent), |  | 
| 262                                       JsonUtf8Encoder.DEFAULT_BUFFER_SIZE); |  | 
| 263     } |  | 
| 264     return new _JsonEncoderSink(sink, _toEncodable, indent); |  | 
| 265   } |  | 
| 266 |  | 
| 267   // Override the base class's bind, to provide a better type. |  | 
| 268   Stream<String> bind(Stream<Object> stream) => super.bind(stream); |  | 
| 269 |  | 
| 270   Converter<Object, dynamic/*=T*/> fuse/*<T>*/( |  | 
| 271       Converter<String, dynamic/*=T*/> other) { |  | 
| 272     if (other is Utf8Encoder) { |  | 
| 273       return new JsonUtf8Encoder(indent, _toEncodable) |  | 
| 274           as dynamic/*=Converter<Object, T>*/; |  | 
| 275     } |  | 
| 276     return super.fuse/*<T>*/(other); |  | 
| 277   } |  | 
| 278 } |  | 
| 279 |  | 
| 280 /** |  | 
| 281  * Encoder that encodes a single object as a UTF-8 encoded JSON string. |  | 
| 282  * |  | 
| 283  * This encoder works equivalently to first converting the object to |  | 
| 284  * a JSON string, and then UTF-8 encoding the string, but without |  | 
| 285  * creating an intermediate string. |  | 
| 286  */ |  | 
| 287 class JsonUtf8Encoder extends Converter<Object, List<int>> { |  | 
| 288   /** Default buffer size used by the JSON-to-UTF-8 encoder. */ |  | 
| 289   static const int DEFAULT_BUFFER_SIZE = 256; |  | 
| 290   /** Indentation used in pretty-print mode, `null` if not pretty. */ |  | 
| 291   final List<int> _indent; |  | 
| 292   /** Function called with each un-encodable object encountered. */ |  | 
| 293   final _ToEncodable _toEncodable; |  | 
| 294   /** UTF-8 buffer size. */ |  | 
| 295   final int _bufferSize; |  | 
| 296 |  | 
| 297   /** |  | 
| 298    * Create converter. |  | 
| 299    * |  | 
| 300    * If [indent] is non-`null`, the converter attempts to "pretty-print" the |  | 
| 301    * JSON, and uses `indent` as the indentation. Otherwise the result has no |  | 
| 302    * whitespace outside of string literals. |  | 
| 303    * If `indent` contains characters that are not valid JSON whitespace |  | 
| 304    * characters, the result will not be valid JSON. JSON whitespace characters |  | 
| 305    * are space (U+0020), tab (U+0009), line feed (U+000a) and carriage return |  | 
| 306    * (U+000d) ([ECMA |  | 
| 307    * 404](http://www.ecma-international.org/publications/standards/Ecma-404.htm)
      ). |  | 
| 308    * |  | 
| 309    * The [bufferSize] is the size of the internal buffers used to collect |  | 
| 310    * UTF-8 code units. |  | 
| 311    * If using [startChunkedConversion], it will be the size of the chunks. |  | 
| 312    * |  | 
| 313    * The JSON encoder handles numbers, strings, booleans, null, lists and maps |  | 
| 314    * directly. |  | 
| 315    * |  | 
| 316    * Any other object is attempted converted by [toEncodable] to an object that |  | 
| 317    * is of one of the convertible types. |  | 
| 318    * |  | 
| 319    * If [toEncodable] is omitted, it defaults to calling `.toJson()` on the |  | 
| 320    * object. |  | 
| 321    */ |  | 
| 322   JsonUtf8Encoder([String indent, |  | 
| 323                    toEncodable(object), |  | 
| 324                    int bufferSize = DEFAULT_BUFFER_SIZE]) |  | 
| 325       : _indent = _utf8Encode(indent), |  | 
| 326         _toEncodable = toEncodable, |  | 
| 327         _bufferSize = bufferSize; |  | 
| 328 |  | 
| 329   static List<int> _utf8Encode(String string) { |  | 
| 330     if (string == null) return null; |  | 
| 331     if (string.isEmpty) return new Uint8List(0); |  | 
| 332     checkAscii: { |  | 
| 333       for (int i = 0; i < string.length; i++) { |  | 
| 334         if (string.codeUnitAt(i) >= 0x80) break checkAscii; |  | 
| 335       } |  | 
| 336       return string.codeUnits; |  | 
| 337     } |  | 
| 338     return UTF8.encode(string); |  | 
| 339   } |  | 
| 340 |  | 
| 341   /** Convert [object] into UTF-8 encoded JSON. */ |  | 
| 342   List<int> convert(Object object) { |  | 
| 343     List<List<int>> bytes = []; |  | 
| 344     // The `stringify` function always converts into chunks. |  | 
| 345     // Collect the chunks into the `bytes` list, then combine them afterwards. |  | 
| 346     void addChunk(Uint8List chunk, int start, int end) { |  | 
| 347       if (start > 0 || end < chunk.length) { |  | 
| 348         int length = end - start; |  | 
| 349         chunk = new Uint8List.view(chunk.buffer, |  | 
| 350                                    chunk.offsetInBytes + start, |  | 
| 351                                    length); |  | 
| 352       } |  | 
| 353       bytes.add(chunk); |  | 
| 354     } |  | 
| 355     _JsonUtf8Stringifier.stringify(object, |  | 
| 356                                    _indent, |  | 
| 357                                    _toEncodable, |  | 
| 358                                    _bufferSize, |  | 
| 359                                    addChunk); |  | 
| 360     if (bytes.length == 1) return bytes[0]; |  | 
| 361     int length = 0; |  | 
| 362     for (int i = 0; i < bytes.length; i++) { |  | 
| 363       length += bytes[i].length; |  | 
| 364     } |  | 
| 365     Uint8List result = new Uint8List(length); |  | 
| 366     for (int i = 0, offset = 0; i < bytes.length; i++) { |  | 
| 367       var byteList = bytes[i]; |  | 
| 368       int end = offset + byteList.length; |  | 
| 369       result.setRange(offset, end, byteList); |  | 
| 370       offset = end; |  | 
| 371     } |  | 
| 372     return result; |  | 
| 373   } |  | 
| 374 |  | 
| 375   /** |  | 
| 376    * Start a chunked conversion. |  | 
| 377    * |  | 
| 378    * Only one object can be passed into the returned sink. |  | 
| 379    * |  | 
| 380    * The argument [sink] will receive byte lists in sizes depending on the |  | 
| 381    * `bufferSize` passed to the constructor when creating this encoder. |  | 
| 382    */ |  | 
| 383   ChunkedConversionSink<Object> startChunkedConversion(Sink<List<int>> sink) { |  | 
| 384     ByteConversionSink byteSink; |  | 
| 385     if (sink is ByteConversionSink) { |  | 
| 386       byteSink = sink; |  | 
| 387     } else { |  | 
| 388       byteSink = new ByteConversionSink.from(sink); |  | 
| 389     } |  | 
| 390     return new _JsonUtf8EncoderSink(byteSink, _toEncodable, |  | 
| 391                                     _indent, _bufferSize); |  | 
| 392   } |  | 
| 393 |  | 
| 394   // Override the base class's bind, to provide a better type. |  | 
| 395   Stream<List<int>> bind(Stream<Object> stream) { |  | 
| 396     return super.bind(stream); |  | 
| 397   } |  | 
| 398 } |  | 
| 399 |  | 
| 400 /** |  | 
| 401  * Implements the chunked conversion from object to its JSON representation. |  | 
| 402  * |  | 
| 403  * The sink only accepts one value, but will produce output in a chunked way. |  | 
| 404  */ |  | 
| 405 class _JsonEncoderSink extends ChunkedConversionSink<Object> { |  | 
| 406   final String _indent; |  | 
| 407   final _ToEncodable _toEncodable; |  | 
| 408   final StringConversionSink _sink; |  | 
| 409   bool _isDone = false; |  | 
| 410 |  | 
| 411   _JsonEncoderSink(this._sink, this._toEncodable, this._indent); |  | 
| 412 |  | 
| 413   /** |  | 
| 414    * Encodes the given object [o]. |  | 
| 415    * |  | 
| 416    * It is an error to invoke this method more than once on any instance. While |  | 
| 417    * this makes the input effectly non-chunked the output will be generated in |  | 
| 418    * a chunked way. |  | 
| 419    */ |  | 
| 420   void add(Object o) { |  | 
| 421     if (_isDone) { |  | 
| 422       throw new StateError("Only one call to add allowed"); |  | 
| 423     } |  | 
| 424     _isDone = true; |  | 
| 425     ClosableStringSink stringSink = _sink.asStringSink(); |  | 
| 426     _JsonStringStringifier.printOn(o, stringSink, _toEncodable, _indent); |  | 
| 427     stringSink.close(); |  | 
| 428   } |  | 
| 429 |  | 
| 430   void close() { /* do nothing */ } |  | 
| 431 } |  | 
| 432 |  | 
| 433 /** |  | 
| 434  * Sink returned when starting a chunked conversion from object to bytes. |  | 
| 435  */ |  | 
| 436 class _JsonUtf8EncoderSink extends ChunkedConversionSink<Object> { |  | 
| 437   /** The byte sink receiveing the encoded chunks. */ |  | 
| 438   final ByteConversionSink _sink; |  | 
| 439   final List<int> _indent; |  | 
| 440   final _ToEncodable _toEncodable; |  | 
| 441   final int _bufferSize; |  | 
| 442   bool _isDone = false; |  | 
| 443   _JsonUtf8EncoderSink(this._sink, this._toEncodable, this._indent, |  | 
| 444                        this._bufferSize); |  | 
| 445 |  | 
| 446   /** Callback called for each slice of result bytes. */ |  | 
| 447   void _addChunk(Uint8List chunk, int start, int end) { |  | 
| 448     _sink.addSlice(chunk, start, end, false); |  | 
| 449   } |  | 
| 450 |  | 
| 451   void add(Object object) { |  | 
| 452     if (_isDone) { |  | 
| 453       throw new StateError("Only one call to add allowed"); |  | 
| 454     } |  | 
| 455     _isDone = true; |  | 
| 456     _JsonUtf8Stringifier.stringify(object, _indent, _toEncodable, |  | 
| 457                                    _bufferSize, |  | 
| 458                                    _addChunk); |  | 
| 459     _sink.close(); |  | 
| 460   } |  | 
| 461 |  | 
| 462   void close() { |  | 
| 463     if (!_isDone) { |  | 
| 464       _isDone = true; |  | 
| 465       _sink.close(); |  | 
| 466     } |  | 
| 467   } |  | 
| 468 } |  | 
| 469 |  | 
| 470 /** |  | 
| 471  * This class parses JSON strings and builds the corresponding objects. |  | 
| 472  */ |  | 
| 473 class JsonDecoder extends Converter<String, Object> { |  | 
| 474   final _Reviver _reviver; |  | 
| 475   /** |  | 
| 476    * Constructs a new JsonDecoder. |  | 
| 477    * |  | 
| 478    * The [reviver] may be `null`. |  | 
| 479    */ |  | 
| 480   const JsonDecoder([reviver(var key, var value)]) : this._reviver = reviver; |  | 
| 481 |  | 
| 482   /** |  | 
| 483    * Converts the given JSON-string [input] to its corresponding object. |  | 
| 484    * |  | 
| 485    * Parsed JSON values are of the types [num], [String], [bool], [Null], |  | 
| 486    * [List]s of parsed JSON values or [Map]s from [String] to parsed JSON |  | 
| 487    * values. |  | 
| 488    * |  | 
| 489    * If `this` was initialized with a reviver, then the parsing operation |  | 
| 490    * invokes the reviver on every object or list property that has been parsed. |  | 
| 491    * The arguments are the property name ([String]) or list index ([int]), and |  | 
| 492    * the value is the parsed value. The return value of the reviver is used as |  | 
| 493    * the value of that property instead the parsed value. |  | 
| 494    * |  | 
| 495    * Throws [FormatException] if the input is not valid JSON text. |  | 
| 496    */ |  | 
| 497   dynamic convert(String input) => _parseJson(input, _reviver); |  | 
| 498 |  | 
| 499   /** |  | 
| 500    * Starts a conversion from a chunked JSON string to its corresponding object. |  | 
| 501    * |  | 
| 502    * The output [sink] receives exactly one decoded element through `add`. |  | 
| 503    */ |  | 
| 504   external StringConversionSink startChunkedConversion(Sink<Object> sink); |  | 
| 505 |  | 
| 506   // Override the base class's bind, to provide a better type. |  | 
| 507   Stream<Object> bind(Stream<String> stream) => super.bind(stream); |  | 
| 508 } |  | 
| 509 |  | 
| 510 // Internal optimized JSON parsing implementation. |  | 
| 511 external _parseJson(String source, reviver(key, value)); |  | 
| 512 |  | 
| 513 |  | 
| 514 // Implementation of encoder/stringifier. |  | 
| 515 |  | 
| 516 dynamic _defaultToEncodable(dynamic object) => object.toJson(); |  | 
| 517 |  | 
| 518 /** |  | 
| 519  * JSON encoder that traverses an object structure and writes JSON source. |  | 
| 520  * |  | 
| 521  * This is an abstract implementation that doesn't decide on the output |  | 
| 522  * format, but writes the JSON through abstract methods like [writeString]. |  | 
| 523  */ |  | 
| 524 abstract class _JsonStringifier { |  | 
| 525   // Character code constants. |  | 
| 526   static const int BACKSPACE       = 0x08; |  | 
| 527   static const int TAB             = 0x09; |  | 
| 528   static const int NEWLINE         = 0x0a; |  | 
| 529   static const int CARRIAGE_RETURN = 0x0d; |  | 
| 530   static const int FORM_FEED       = 0x0c; |  | 
| 531   static const int QUOTE           = 0x22; |  | 
| 532   static const int CHAR_0          = 0x30; |  | 
| 533   static const int BACKSLASH       = 0x5c; |  | 
| 534   static const int CHAR_b          = 0x62; |  | 
| 535   static const int CHAR_f          = 0x66; |  | 
| 536   static const int CHAR_n          = 0x6e; |  | 
| 537   static const int CHAR_r          = 0x72; |  | 
| 538   static const int CHAR_t          = 0x74; |  | 
| 539   static const int CHAR_u          = 0x75; |  | 
| 540 |  | 
| 541   /** List of objects currently being traversed. Used to detect cycles. */ |  | 
| 542   final List _seen = new List(); |  | 
| 543   /** Function called for each un-encodable object encountered. */ |  | 
| 544   final _ToEncodable _toEncodable; |  | 
| 545 |  | 
| 546   _JsonStringifier(toEncodable(o)) |  | 
| 547       : _toEncodable = toEncodable ?? _defaultToEncodable; |  | 
| 548 |  | 
| 549   /** Append a string to the JSON output. */ |  | 
| 550   void writeString(String characters); |  | 
| 551   /** Append part of a string to the JSON output. */ |  | 
| 552   void writeStringSlice(String characters, int start, int end); |  | 
| 553   /** Append a single character, given by its code point, to the JSON output. */ |  | 
| 554   void writeCharCode(int charCode); |  | 
| 555   /** Write a number to the JSON output. */ |  | 
| 556   void writeNumber(num number); |  | 
| 557 |  | 
| 558   // ('0' + x) or ('a' + x - 10) |  | 
| 559   static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x; |  | 
| 560 |  | 
| 561   /** |  | 
| 562    * Write, and suitably escape, a string's content as a JSON string literal. |  | 
| 563    */ |  | 
| 564   void writeStringContent(String s) { |  | 
| 565     int offset = 0; |  | 
| 566     final int length = s.length; |  | 
| 567     for (int i = 0; i < length; i++) { |  | 
| 568       int charCode = s.codeUnitAt(i); |  | 
| 569       if (charCode > BACKSLASH) continue; |  | 
| 570       if (charCode < 32) { |  | 
| 571         if (i > offset) writeStringSlice(s, offset, i); |  | 
| 572         offset = i + 1; |  | 
| 573         writeCharCode(BACKSLASH); |  | 
| 574         switch (charCode) { |  | 
| 575         case BACKSPACE: |  | 
| 576           writeCharCode(CHAR_b); |  | 
| 577           break; |  | 
| 578         case TAB: |  | 
| 579           writeCharCode(CHAR_t); |  | 
| 580           break; |  | 
| 581         case NEWLINE: |  | 
| 582           writeCharCode(CHAR_n); |  | 
| 583           break; |  | 
| 584         case FORM_FEED: |  | 
| 585           writeCharCode(CHAR_f); |  | 
| 586           break; |  | 
| 587         case CARRIAGE_RETURN: |  | 
| 588           writeCharCode(CHAR_r); |  | 
| 589           break; |  | 
| 590         default: |  | 
| 591           writeCharCode(CHAR_u); |  | 
| 592           writeCharCode(CHAR_0); |  | 
| 593           writeCharCode(CHAR_0); |  | 
| 594           writeCharCode(hexDigit((charCode >> 4) & 0xf)); |  | 
| 595           writeCharCode(hexDigit(charCode & 0xf)); |  | 
| 596           break; |  | 
| 597         } |  | 
| 598       } else if (charCode == QUOTE || charCode == BACKSLASH) { |  | 
| 599         if (i > offset) writeStringSlice(s, offset, i); |  | 
| 600         offset = i + 1; |  | 
| 601         writeCharCode(BACKSLASH); |  | 
| 602         writeCharCode(charCode); |  | 
| 603       } |  | 
| 604     } |  | 
| 605     if (offset == 0) { |  | 
| 606       writeString(s); |  | 
| 607     } else if (offset < length) { |  | 
| 608       writeStringSlice(s, offset, length); |  | 
| 609     } |  | 
| 610   } |  | 
| 611 |  | 
| 612   /** |  | 
| 613    * Check if an encountered object is already being traversed. |  | 
| 614    * |  | 
| 615    * Records the object if it isn't already seen. Should have a matching call to |  | 
| 616    * [_removeSeen] when the object is no longer being traversed. |  | 
| 617    */ |  | 
| 618   void _checkCycle(object) { |  | 
| 619     for (int i = 0; i < _seen.length; i++) { |  | 
| 620       if (identical(object, _seen[i])) { |  | 
| 621         throw new JsonCyclicError(object); |  | 
| 622       } |  | 
| 623     } |  | 
| 624     _seen.add(object); |  | 
| 625   } |  | 
| 626 |  | 
| 627   /** |  | 
| 628    * Remove [object] from the list of currently traversed objects. |  | 
| 629    * |  | 
| 630    * Should be called in the opposite order of the matching [_checkCycle] |  | 
| 631    * calls. |  | 
| 632    */ |  | 
| 633   void _removeSeen(object) { |  | 
| 634     assert(!_seen.isEmpty); |  | 
| 635     assert(identical(_seen.last, object)); |  | 
| 636     _seen.removeLast(); |  | 
| 637   } |  | 
| 638 |  | 
| 639   /** |  | 
| 640    * Write an object. |  | 
| 641    * |  | 
| 642    * If [object] isn't directly encodable, the [_toEncodable] function gets one |  | 
| 643    * chance to return a replacement which is encodable. |  | 
| 644    */ |  | 
| 645   void writeObject(object) { |  | 
| 646     // Tries stringifying object directly. If it's not a simple value, List or |  | 
| 647     // Map, call toJson() to get a custom representation and try serializing |  | 
| 648     // that. |  | 
| 649     if (writeJsonValue(object)) return; |  | 
| 650     _checkCycle(object); |  | 
| 651     try { |  | 
| 652       var customJson = _toEncodable(object); |  | 
| 653       if (!writeJsonValue(customJson)) { |  | 
| 654         throw new JsonUnsupportedObjectError(object); |  | 
| 655       } |  | 
| 656       _removeSeen(object); |  | 
| 657     } catch (e) { |  | 
| 658       throw new JsonUnsupportedObjectError(object, cause: e); |  | 
| 659     } |  | 
| 660   } |  | 
| 661 |  | 
| 662   /** |  | 
| 663    * Serialize a [num], [String], [bool], [Null], [List] or [Map] value. |  | 
| 664    * |  | 
| 665    * Returns true if the value is one of these types, and false if not. |  | 
| 666    * If a value is both a [List] and a [Map], it's serialized as a [List]. |  | 
| 667    */ |  | 
| 668   bool writeJsonValue(object) { |  | 
| 669     if (object is num) { |  | 
| 670       if (!object.isFinite) return false; |  | 
| 671       writeNumber(object); |  | 
| 672       return true; |  | 
| 673     } else if (identical(object, true)) { |  | 
| 674       writeString('true'); |  | 
| 675       return true; |  | 
| 676     } else if (identical(object, false)) { |  | 
| 677       writeString('false'); |  | 
| 678        return true; |  | 
| 679     } else if (object == null) { |  | 
| 680       writeString('null'); |  | 
| 681       return true; |  | 
| 682     } else if (object is String) { |  | 
| 683       writeString('"'); |  | 
| 684       writeStringContent(object); |  | 
| 685       writeString('"'); |  | 
| 686       return true; |  | 
| 687     } else if (object is List) { |  | 
| 688       _checkCycle(object); |  | 
| 689       writeList(object); |  | 
| 690       _removeSeen(object); |  | 
| 691       return true; |  | 
| 692     } else if (object is Map) { |  | 
| 693       _checkCycle(object); |  | 
| 694       // writeMap can fail if keys are not all strings. |  | 
| 695       var success = writeMap(object); |  | 
| 696       _removeSeen(object); |  | 
| 697       return success; |  | 
| 698     } else { |  | 
| 699       return false; |  | 
| 700     } |  | 
| 701   } |  | 
| 702 |  | 
| 703   /** Serialize a [List]. */ |  | 
| 704   void writeList(List list) { |  | 
| 705     writeString('['); |  | 
| 706     if (list.length > 0) { |  | 
| 707       writeObject(list[0]); |  | 
| 708       for (int i = 1; i < list.length; i++) { |  | 
| 709         writeString(','); |  | 
| 710         writeObject(list[i]); |  | 
| 711       } |  | 
| 712     } |  | 
| 713     writeString(']'); |  | 
| 714   } |  | 
| 715 |  | 
| 716   /** Serialize a [Map]. */ |  | 
| 717   bool writeMap(Map map) { |  | 
| 718     if (map.isEmpty) { |  | 
| 719       writeString("{}"); |  | 
| 720       return true; |  | 
| 721     } |  | 
| 722     List keyValueList = new List(map.length * 2); |  | 
| 723     int i = 0; |  | 
| 724     bool allStringKeys = true; |  | 
| 725     map.forEach((key, value) { |  | 
| 726       if (key is! String) { |  | 
| 727         allStringKeys = false; |  | 
| 728       } |  | 
| 729       keyValueList[i++] = key; |  | 
| 730       keyValueList[i++] = value; |  | 
| 731     }); |  | 
| 732     if (!allStringKeys) return false; |  | 
| 733     writeString('{'); |  | 
| 734     String separator = '"'; |  | 
| 735     for (int i = 0; i < keyValueList.length; i += 2) { |  | 
| 736       writeString(separator); |  | 
| 737       separator = ',"'; |  | 
| 738       writeStringContent(keyValueList[i]); |  | 
| 739       writeString('":'); |  | 
| 740       writeObject(keyValueList[i + 1]); |  | 
| 741     } |  | 
| 742     writeString('}'); |  | 
| 743     return true; |  | 
| 744   } |  | 
| 745 } |  | 
| 746 |  | 
| 747 /** |  | 
| 748  * A modification of [_JsonStringifier] which indents the contents of [List] and |  | 
| 749  * [Map] objects using the specified indent value. |  | 
| 750  * |  | 
| 751  * Subclasses should implement [writeIndentation]. |  | 
| 752  */ |  | 
| 753 abstract class _JsonPrettyPrintMixin implements _JsonStringifier { |  | 
| 754   int _indentLevel = 0; |  | 
| 755 |  | 
| 756   /** |  | 
| 757    * Add [indentLevel] indentations to the JSON output. |  | 
| 758    */ |  | 
| 759   void writeIndentation(int indentLevel); |  | 
| 760 |  | 
| 761   void writeList(List list) { |  | 
| 762     if (list.isEmpty) { |  | 
| 763       writeString('[]'); |  | 
| 764     } else { |  | 
| 765       writeString('[\n'); |  | 
| 766       _indentLevel++; |  | 
| 767       writeIndentation(_indentLevel); |  | 
| 768       writeObject(list[0]); |  | 
| 769       for (int i = 1; i < list.length; i++) { |  | 
| 770         writeString(',\n'); |  | 
| 771         writeIndentation(_indentLevel); |  | 
| 772         writeObject(list[i]); |  | 
| 773       } |  | 
| 774       writeString('\n'); |  | 
| 775       _indentLevel--; |  | 
| 776       writeIndentation(_indentLevel); |  | 
| 777       writeString(']'); |  | 
| 778     } |  | 
| 779   } |  | 
| 780 |  | 
| 781   bool writeMap(Map map) { |  | 
| 782     if (map.isEmpty) { |  | 
| 783       writeString("{}"); |  | 
| 784       return true; |  | 
| 785     } |  | 
| 786     List keyValueList = new List(map.length * 2); |  | 
| 787     int i = 0; |  | 
| 788     bool allStringKeys = true; |  | 
| 789     map.forEach((key, value) { |  | 
| 790       if (key is! String) { |  | 
| 791         allStringKeys = false; |  | 
| 792       } |  | 
| 793       keyValueList[i++] = key; |  | 
| 794       keyValueList[i++] = value; |  | 
| 795     }); |  | 
| 796     if (!allStringKeys) return false; |  | 
| 797     writeString('{\n'); |  | 
| 798     _indentLevel++; |  | 
| 799     String separator = ""; |  | 
| 800     for (int i = 0; i < keyValueList.length; i += 2) { |  | 
| 801       writeString(separator); |  | 
| 802       separator = ",\n"; |  | 
| 803       writeIndentation(_indentLevel); |  | 
| 804       writeString('"'); |  | 
| 805       writeStringContent(keyValueList[i]); |  | 
| 806       writeString('": '); |  | 
| 807       writeObject(keyValueList[i + 1]); |  | 
| 808     } |  | 
| 809     writeString('\n'); |  | 
| 810     _indentLevel--; |  | 
| 811     writeIndentation(_indentLevel); |  | 
| 812     writeString('}'); |  | 
| 813     return true; |  | 
| 814   } |  | 
| 815 } |  | 
| 816 |  | 
| 817 /** |  | 
| 818  * A specialziation of [_JsonStringifier] that writes its JSON to a string. |  | 
| 819  */ |  | 
| 820 class _JsonStringStringifier extends _JsonStringifier { |  | 
| 821   final StringSink _sink; |  | 
| 822 |  | 
| 823   _JsonStringStringifier(this._sink, _toEncodable) : super(_toEncodable); |  | 
| 824 |  | 
| 825   /** |  | 
| 826    * Convert object to a string. |  | 
| 827    * |  | 
| 828    * The [toEncodable] function is used to convert non-encodable objects |  | 
| 829    * to encodable ones. |  | 
| 830    * |  | 
| 831    * If [indent] is not `null`, the resulting JSON will be "pretty-printed" |  | 
| 832    * with newlines and indentation. The `indent` string is added as indentation |  | 
| 833    * for each indentation level. It should only contain valid JSON whitespace |  | 
| 834    * characters (space, tab, carriage return or line feed). |  | 
| 835    */ |  | 
| 836   static String stringify(object, toEncodable(o), String indent) { |  | 
| 837     StringBuffer output = new StringBuffer(); |  | 
| 838     printOn(object, output, toEncodable, indent); |  | 
| 839     return output.toString(); |  | 
| 840   } |  | 
| 841 |  | 
| 842   /** |  | 
| 843    * Convert object to a string, and write the result to the [output] sink. |  | 
| 844    * |  | 
| 845    * The result is written piecemally to the sink. |  | 
| 846    */ |  | 
| 847   static void printOn( |  | 
| 848       object, StringSink output, toEncodable(o), String indent) { |  | 
| 849     var stringifier; |  | 
| 850     if (indent == null) { |  | 
| 851       stringifier = new _JsonStringStringifier(output, toEncodable); |  | 
| 852     } else { |  | 
| 853       stringifier = |  | 
| 854           new _JsonStringStringifierPretty(output, toEncodable, indent); |  | 
| 855     } |  | 
| 856     stringifier.writeObject(object); |  | 
| 857   } |  | 
| 858 |  | 
| 859   void writeNumber(num number) { |  | 
| 860     _sink.write(number.toString()); |  | 
| 861   } |  | 
| 862   void writeString(String string) { |  | 
| 863     _sink.write(string); |  | 
| 864   } |  | 
| 865   void writeStringSlice(String string, int start, int end) { |  | 
| 866     _sink.write(string.substring(start, end)); |  | 
| 867   } |  | 
| 868   void writeCharCode(int charCode) { |  | 
| 869     _sink.writeCharCode(charCode); |  | 
| 870   } |  | 
| 871 } |  | 
| 872 |  | 
| 873 class _JsonStringStringifierPretty extends _JsonStringStringifier |  | 
| 874                                    with _JsonPrettyPrintMixin { |  | 
| 875   final String _indent; |  | 
| 876 |  | 
| 877   _JsonStringStringifierPretty(StringSink sink, toEncodable(o), this._indent) |  | 
| 878       : super(sink, toEncodable); |  | 
| 879 |  | 
| 880   void writeIndentation(int count) { |  | 
| 881     for (int i = 0; i < count; i++) writeString(_indent); |  | 
| 882   } |  | 
| 883 } |  | 
| 884 |  | 
| 885 typedef void _AddChunk(Uint8List list, int start, int end); |  | 
| 886 |  | 
| 887 /** |  | 
| 888  * Specialization of [_JsonStringifier] that writes the JSON as UTF-8. |  | 
| 889  * |  | 
| 890  * The JSON text is UTF-8 encoded and written to [Uint8List] buffers. |  | 
| 891  * The buffers are then passed back to a user provided callback method. |  | 
| 892  */ |  | 
| 893 class _JsonUtf8Stringifier extends _JsonStringifier { |  | 
| 894   final int bufferSize; |  | 
| 895   final _AddChunk addChunk; |  | 
| 896   Uint8List buffer; |  | 
| 897   int index = 0; |  | 
| 898 |  | 
| 899   _JsonUtf8Stringifier(toEncodable(o), int bufferSize, this.addChunk) |  | 
| 900       : this.bufferSize = bufferSize, |  | 
| 901         buffer = new Uint8List(bufferSize), |  | 
| 902         super(toEncodable); |  | 
| 903 |  | 
| 904   /** |  | 
| 905    * Convert [object] to UTF-8 encoded JSON. |  | 
| 906    * |  | 
| 907    * Calls [addChunk] with slices of UTF-8 code units. |  | 
| 908    * These will typically have size [bufferSize], but may be shorter. |  | 
| 909    * The buffers are not reused, so the [addChunk] call may keep and reuse the |  | 
| 910    * chunks. |  | 
| 911    * |  | 
| 912    * If [indent] is non-`null`, the result will be "pretty-printed" with extra |  | 
| 913    * newlines and indentation, using [indent] as the indentation. |  | 
| 914    */ |  | 
| 915   static void stringify(Object object, |  | 
| 916                         List<int> indent, |  | 
| 917                         toEncodable(o), |  | 
| 918                         int bufferSize, |  | 
| 919                         void addChunk(Uint8List chunk, int start, int end)) { |  | 
| 920     _JsonUtf8Stringifier stringifier; |  | 
| 921     if (indent != null) { |  | 
| 922       stringifier = new _JsonUtf8StringifierPretty(toEncodable, indent, |  | 
| 923                                                    bufferSize, addChunk); |  | 
| 924     } else { |  | 
| 925       stringifier = new _JsonUtf8Stringifier(toEncodable, bufferSize, addChunk); |  | 
| 926     } |  | 
| 927     stringifier.writeObject(object); |  | 
| 928     stringifier.flush(); |  | 
| 929   } |  | 
| 930 |  | 
| 931   /** |  | 
| 932    * Must be called at the end to push the last chunk to the [addChunk] |  | 
| 933    * callback. |  | 
| 934    */ |  | 
| 935   void flush() { |  | 
| 936     if (index > 0) { |  | 
| 937       addChunk(buffer, 0, index); |  | 
| 938     } |  | 
| 939     buffer = null; |  | 
| 940     index = 0; |  | 
| 941   } |  | 
| 942 |  | 
| 943   void writeNumber(num number) { |  | 
| 944     writeAsciiString(number.toString()); |  | 
| 945   } |  | 
| 946 |  | 
| 947   /** Write a string that is known to not have non-ASCII characters. */ |  | 
| 948   void writeAsciiString(String string) { |  | 
| 949     // TODO(lrn): Optimize by copying directly into buffer instead of going |  | 
| 950     // through writeCharCode; |  | 
| 951     for (int i = 0; i < string.length; i++) { |  | 
| 952       int char = string.codeUnitAt(i); |  | 
| 953       assert(char <= 0x7f); |  | 
| 954       writeByte(char); |  | 
| 955     } |  | 
| 956   } |  | 
| 957 |  | 
| 958   void writeString(String string) { |  | 
| 959     writeStringSlice(string, 0, string.length); |  | 
| 960   } |  | 
| 961 |  | 
| 962   void writeStringSlice(String string, int start, int end) { |  | 
| 963     // TODO(lrn): Optimize by copying directly into buffer instead of going |  | 
| 964     // through writeCharCode/writeByte. Assumption is the most characters |  | 
| 965     // in starings are plain ASCII. |  | 
| 966     for (int i = start; i < end; i++) { |  | 
| 967       int char = string.codeUnitAt(i); |  | 
| 968       if (char <= 0x7f) { |  | 
| 969         writeByte(char); |  | 
| 970       } else { |  | 
| 971         if ((char & 0xFC00) == 0xD800 && i + 1 < end) { |  | 
| 972           // Lead surrogate. |  | 
| 973           int nextChar = string.codeUnitAt(i + 1); |  | 
| 974           if ((nextChar & 0xFC00) == 0xDC00) { |  | 
| 975             // Tail surrogate. |  | 
| 976             char = 0x10000 + ((char & 0x3ff) << 10) + (nextChar & 0x3ff); |  | 
| 977             writeFourByteCharCode(char); |  | 
| 978             i++; |  | 
| 979             continue; |  | 
| 980           } |  | 
| 981         } |  | 
| 982         writeMultiByteCharCode(char); |  | 
| 983       } |  | 
| 984     } |  | 
| 985   } |  | 
| 986 |  | 
| 987   void writeCharCode(int charCode) { |  | 
| 988     if (charCode <= 0x7f) { |  | 
| 989       writeByte(charCode); |  | 
| 990       return; |  | 
| 991     } |  | 
| 992     writeMultiByteCharCode(charCode); |  | 
| 993   } |  | 
| 994 |  | 
| 995   void writeMultiByteCharCode(int charCode) { |  | 
| 996     if (charCode <= 0x7ff) { |  | 
| 997       writeByte(0xC0 | (charCode >> 6)); |  | 
| 998       writeByte(0x80 | (charCode & 0x3f)); |  | 
| 999       return; |  | 
| 1000     } |  | 
| 1001     if (charCode <= 0xffff) { |  | 
| 1002       writeByte(0xE0 | (charCode >> 12)); |  | 
| 1003       writeByte(0x80 | ((charCode >> 6) & 0x3f)); |  | 
| 1004       writeByte(0x80 | (charCode & 0x3f)); |  | 
| 1005       return; |  | 
| 1006     } |  | 
| 1007     writeFourByteCharCode(charCode); |  | 
| 1008   } |  | 
| 1009 |  | 
| 1010   void writeFourByteCharCode(int charCode) { |  | 
| 1011     assert(charCode <= 0x10ffff); |  | 
| 1012     writeByte(0xF0 | (charCode >> 18)); |  | 
| 1013     writeByte(0x80 | ((charCode >> 12) & 0x3f)); |  | 
| 1014     writeByte(0x80 | ((charCode >> 6) & 0x3f)); |  | 
| 1015     writeByte(0x80 | (charCode & 0x3f)); |  | 
| 1016   } |  | 
| 1017 |  | 
| 1018   void writeByte(int byte) { |  | 
| 1019     assert(byte <= 0xff); |  | 
| 1020     if (index == buffer.length) { |  | 
| 1021       addChunk(buffer, 0, index); |  | 
| 1022       buffer = new Uint8List(bufferSize); |  | 
| 1023       index = 0; |  | 
| 1024     } |  | 
| 1025     buffer[index++] = byte; |  | 
| 1026   } |  | 
| 1027 } |  | 
| 1028 |  | 
| 1029 /** |  | 
| 1030  * Pretty-printing version of [_JsonUtf8Stringifier]. |  | 
| 1031  */ |  | 
| 1032 class _JsonUtf8StringifierPretty extends _JsonUtf8Stringifier |  | 
| 1033                                  with _JsonPrettyPrintMixin { |  | 
| 1034   final List<int> indent; |  | 
| 1035   _JsonUtf8StringifierPretty( |  | 
| 1036       toEncodable(o), this.indent, |  | 
| 1037       bufferSize, void addChunk(Uint8List buffer, int start, int end)) |  | 
| 1038       : super(toEncodable, bufferSize, addChunk); |  | 
| 1039 |  | 
| 1040   void writeIndentation(int count) { |  | 
| 1041     List<int> indent = this.indent; |  | 
| 1042     int indentLength = indent.length; |  | 
| 1043     if (indentLength == 1) { |  | 
| 1044       int char = indent[0]; |  | 
| 1045       while (count > 0) { |  | 
| 1046         writeByte(char); |  | 
| 1047         count -= 1; |  | 
| 1048       } |  | 
| 1049       return; |  | 
| 1050     } |  | 
| 1051     while (count > 0) { |  | 
| 1052       count--; |  | 
| 1053       int end = index + indentLength; |  | 
| 1054       if (end <= buffer.length) { |  | 
| 1055         buffer.setRange(index, end, indent); |  | 
| 1056         index = end; |  | 
| 1057       } else { |  | 
| 1058         for (int i = 0; i < indentLength; i++) { |  | 
| 1059           writeByte(indent[i]); |  | 
| 1060         } |  | 
| 1061       } |  | 
| 1062     } |  | 
| 1063   } |  | 
| 1064 } |  | 
| OLD | NEW | 
|---|