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