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 |