Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(349)

Side by Side Diff: sdk/lib/convert/json.dart

Issue 689043002: Make JSON.fuse(UTF8) be more efficient by not creating intermediate string. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address comments. Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sdk/lib/_internal/compiler/js_lib/convert_patch.dart ('k') | sdk/lib/convert/utf.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of dart.convert; 5 part of dart.convert;
6 6
7 /** 7 /**
8 * Error thrown by JSON serialization if an object cannot be serialized. 8 * Error thrown by JSON serialization if an object cannot be serialized.
9 * 9 *
10 * The [unsupportedObject] field holds that object that failed to be serialized. 10 * The [unsupportedObject] field holds that object that failed to be serialized.
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after
160 /** 160 /**
161 * The string used for indention. 161 * The string used for indention.
162 * 162 *
163 * When generating multi-line output, this string is inserted once at the 163 * When generating multi-line output, this string is inserted once at the
164 * beginning of each indented line for each level of indentation. 164 * beginning of each indented line for each level of indentation.
165 * 165 *
166 * If `null`, the output is encoded as a single line. 166 * If `null`, the output is encoded as a single line.
167 */ 167 */
168 final String indent; 168 final String indent;
169 169
170 final _toEncodableFunction; 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;
171 175
172 /** 176 /**
173 * Creates a JSON encoder. 177 * Creates a JSON encoder.
174 * 178 *
175 * The JSON encoder handles numbers, strings, booleans, null, lists and 179 * The JSON encoder handles numbers, strings, booleans, null, lists and
176 * maps directly. 180 * maps directly.
177 * 181 *
178 * Any other object is attempted converted by [toEncodable] to an 182 * Any other object is attempted converted by [toEncodable] to an
179 * object that is of one of the convertible types. 183 * object that is of one of the convertible types.
180 * 184 *
181 * If [toEncodable] is omitted, it defaults to calling `.toJson()` on 185 * If [toEncodable] is omitted, it defaults to calling `.toJson()` on
182 * the object. 186 * the object.
183 */ 187 */
184 const JsonEncoder([Object toEncodable(Object nonSerializable)]) 188 const JsonEncoder([Object toEncodable(Object nonSerializable)])
185 : this.indent = null, 189 : this.indent = null,
186 this._toEncodableFunction = toEncodable; 190 this._toEncodable = toEncodable;
187 191
188 /** 192 /**
189 * Creates a JSON encoder that creates multi-line JSON. 193 * Creates a JSON encoder that creates multi-line JSON.
190 * 194 *
191 * The encoding of elements of lists and maps are indented and put on separate 195 * The encoding of elements of lists and maps are indented and put on separate
192 * lines. The [indent] string is prepended to these elements, once for each 196 * lines. The [indent] string is prepended to these elements, once for each
193 * level of indentation. 197 * level of indentation.
194 * 198 *
195 * If [indent] is `null`, the output is encoded as a single line. 199 * If [indent] is `null`, the output is encoded as a single line.
196 * 200 *
197 * The JSON encoder handles numbers, strings, booleans, null, lists and 201 * The JSON encoder handles numbers, strings, booleans, null, lists and
198 * maps directly. 202 * maps directly.
199 * 203 *
200 * Any other object is attempted converted by [toEncodable] to an 204 * Any other object is attempted converted by [toEncodable] to an
201 * object that is of one of the convertible types. 205 * object that is of one of the convertible types.
202 * 206 *
203 * If [toEncodable] is omitted, it defaults to calling `.toJson()` on 207 * If [toEncodable] is omitted, it defaults to calling `.toJson()` on
204 * the object. 208 * the object.
205 */ 209 */
206 const JsonEncoder.withIndent(this.indent, 210 const JsonEncoder.withIndent(this.indent,
207 [Object toEncodable(Object nonSerializable)]) 211 [Object toEncodable(Object nonSerializable)])
208 : this._toEncodableFunction = toEncodable; 212 : this._toEncodable = toEncodable;
209 213
210 /** 214 /**
211 * Converts [object] to a JSON [String]. 215 * Converts [object] to a JSON [String].
212 * 216 *
213 * Directly serializable values are [num], [String], [bool], and [Null], as 217 * Directly serializable values are [num], [String], [bool], and [Null], as
214 * well as some [List] and [Map] values. 218 * well as some [List] and [Map] values.
215 * For [List], the elements must all be serializable. 219 * For [List], the elements must all be serializable.
216 * For [Map], the keys must be [String] and the values must be serializable. 220 * For [Map], the keys must be [String] and the values must be serializable.
217 * 221 *
218 * If a value is any other type is attempted serialized, the conversion 222 * If a value is any other type is attempted serialized, the conversion
(...skipping 10 matching lines...) Expand all
229 * other lists or maps, it cannot be serialized and a [JsonCyclicError] is 233 * other lists or maps, it cannot be serialized and a [JsonCyclicError] is
230 * thrown. 234 * thrown.
231 * 235 *
232 * [object] should not change during serialization. 236 * [object] should not change during serialization.
233 * 237 *
234 * If an object is serialized more than once, [convert] may cache the text 238 * If an object is serialized more than once, [convert] may cache the text
235 * for it. In other words, if the content of an object changes after it is 239 * for it. In other words, if the content of an object changes after it is
236 * first serialized, the new values may not be reflected in the result. 240 * first serialized, the new values may not be reflected in the result.
237 */ 241 */
238 String convert(Object object) => 242 String convert(Object object) =>
239 _JsonStringifier.stringify(object, _toEncodableFunction, indent); 243 _JsonStringStringifier.stringify(object, _toEncodable, indent);
240 244
241 /** 245 /**
242 * Starts a chunked conversion. 246 * Starts a chunked conversion.
243 * 247 *
244 * The converter works more efficiently if the given [sink] is a 248 * The converter works more efficiently if the given [sink] is a
245 * [StringConversionSink]. 249 * [StringConversionSink].
246 * 250 *
247 * Returns a chunked-conversion sink that accepts at most one object. It is 251 * Returns a chunked-conversion sink that accepts at most one object. It is
248 * an error to invoke `add` more than once on the returned sink. 252 * an error to invoke `add` more than once on the returned sink.
249 */ 253 */
250 ChunkedConversionSink<Object> startChunkedConversion(Sink<String> sink) { 254 ChunkedConversionSink<Object> startChunkedConversion(Sink<String> sink) {
251 if (sink is! StringConversionSink) { 255 if (sink is! StringConversionSink) {
252 sink = new StringConversionSink.from(sink); 256 sink = new StringConversionSink.from(sink);
257 } else if (sink is _Utf8EncoderSink) {
258 return new _JsonUtf8EncoderSink(sink._sink, _toEncodable,
259 JsonUtf8Encoder._utf8Encode(indent),
260 _JsonUtf8EncoderSink.DEFAULT_BUFFER_SIZE);
253 } 261 }
254 return new _JsonEncoderSink(sink, _toEncodableFunction, indent); 262 return new _JsonEncoderSink(sink, _toEncodable, indent);
255 } 263 }
256 264
257 // Override the base-classes bind, to provide a better type. 265 // Override the base-classes bind, to provide a better type.
258 Stream<String> bind(Stream<Object> stream) => super.bind(stream); 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 }
259 } 274 }
260 275
261 /** 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 /**
262 * Implements the chunked conversion from object to its JSON representation. 400 * Implements the chunked conversion from object to its JSON representation.
263 * 401 *
264 * The sink only accepts one value, but will produce output in a chunked way. 402 * The sink only accepts one value, but will produce output in a chunked way.
265 */ 403 */
266 class _JsonEncoderSink extends ChunkedConversionSink<Object> { 404 class _JsonEncoderSink extends ChunkedConversionSink<Object> {
267 final String _indent; 405 final String _indent;
268 final Function _toEncodableFunction; 406 final Function _toEncodable;
269 final StringConversionSink _sink; 407 final StringConversionSink _sink;
270 bool _isDone = false; 408 bool _isDone = false;
271 409
272 _JsonEncoderSink(this._sink, this._toEncodableFunction, this._indent); 410 _JsonEncoderSink(this._sink, this._toEncodable, this._indent);
273 411
274 /** 412 /**
275 * Encodes the given object [o]. 413 * Encodes the given object [o].
276 * 414 *
277 * It is an error to invoke this method more than once on any instance. While 415 * It is an error to invoke this method more than once on any instance. While
278 * this makes the input effectly non-chunked the output will be generated in 416 * this makes the input effectly non-chunked the output will be generated in
279 * a chunked way. 417 * a chunked way.
280 */ 418 */
281 void add(Object o) { 419 void add(Object o) {
282 if (_isDone) { 420 if (_isDone) {
283 throw new StateError("Only one call to add allowed"); 421 throw new StateError("Only one call to add allowed");
284 } 422 }
285 _isDone = true; 423 _isDone = true;
286 ClosableStringSink stringSink = _sink.asStringSink(); 424 ClosableStringSink stringSink = _sink.asStringSink();
287 _JsonStringifier.printOn(o, stringSink, _toEncodableFunction, _indent); 425 _JsonStringStringifier.printOn(o, stringSink, _toEncodable, _indent);
288 stringSink.close(); 426 stringSink.close();
289 } 427 }
290 428
291 void close() { /* do nothing */ } 429 void close() { /* do nothing */ }
292 } 430 }
293 431
294 /** 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 /**
295 * This class parses JSON strings and builds the corresponding objects. 470 * This class parses JSON strings and builds the corresponding objects.
296 */ 471 */
297 class JsonDecoder extends Converter<String, Object> { 472 class JsonDecoder extends Converter<String, Object> {
298 final _Reviver _reviver; 473 final _Reviver _reviver;
299 /** 474 /**
300 * Constructs a new JsonDecoder. 475 * Constructs a new JsonDecoder.
301 * 476 *
302 * The [reviver] may be `null`. 477 * The [reviver] may be `null`.
303 */ 478 */
304 const JsonDecoder([reviver(var key, var value)]) : this._reviver = reviver; 479 const JsonDecoder([reviver(var key, var value)]) : this._reviver = reviver;
(...skipping 28 matching lines...) Expand all
333 } 508 }
334 509
335 // Internal optimized JSON parsing implementation. 510 // Internal optimized JSON parsing implementation.
336 external _parseJson(String source, reviver(key, value)); 511 external _parseJson(String source, reviver(key, value));
337 512
338 513
339 // Implementation of encoder/stringifier. 514 // Implementation of encoder/stringifier.
340 515
341 Object _defaultToEncodable(object) => object.toJson(); 516 Object _defaultToEncodable(object) => object.toJson();
342 517
343 class _JsonStringifier { 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 {
344 // Character code constants. 525 // Character code constants.
345 static const int BACKSPACE = 0x08; 526 static const int BACKSPACE = 0x08;
346 static const int TAB = 0x09; 527 static const int TAB = 0x09;
347 static const int NEWLINE = 0x0a; 528 static const int NEWLINE = 0x0a;
348 static const int CARRIAGE_RETURN = 0x0d; 529 static const int CARRIAGE_RETURN = 0x0d;
349 static const int FORM_FEED = 0x0c; 530 static const int FORM_FEED = 0x0c;
350 static const int QUOTE = 0x22; 531 static const int QUOTE = 0x22;
351 static const int CHAR_0 = 0x30; 532 static const int CHAR_0 = 0x30;
352 static const int BACKSLASH = 0x5c; 533 static const int BACKSLASH = 0x5c;
353 static const int CHAR_b = 0x62; 534 static const int CHAR_b = 0x62;
354 static const int CHAR_f = 0x66; 535 static const int CHAR_f = 0x66;
355 static const int CHAR_n = 0x6e; 536 static const int CHAR_n = 0x6e;
356 static const int CHAR_r = 0x72; 537 static const int CHAR_r = 0x72;
357 static const int CHAR_t = 0x74; 538 static const int CHAR_t = 0x74;
358 static const int CHAR_u = 0x75; 539 static const int CHAR_u = 0x75;
359 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. */
360 final Function _toEncodable; 544 final Function _toEncodable;
361 final StringSink _sink;
362 final List _seen;
363 545
364 factory _JsonStringifier(StringSink sink, Function toEncodable, 546 _JsonStringifier(Object _toEncodable(Object o))
365 String indent) { 547 : _toEncodable = (_toEncodable != null) ? _toEncodable
366 if (indent == null) return new _JsonStringifier._(sink, toEncodable); 548 : _defaultToEncodable;
367 return new _JsonStringifierPretty(sink, toEncodable, indent);
368 }
369 549
370 _JsonStringifier._(this._sink, this._toEncodable) 550 /** Append a string to the JSON output. */
371 : this._seen = new List(); 551 void writeString(String characters);
372 552 /** Append part of a string to the JSON output. */
373 static String stringify(object, toEncodable(object), String indent) { 553 void writeStringSlice(String characters, int start, int end);
374 if (toEncodable == null) toEncodable = _defaultToEncodable; 554 /** Append a single character, given by its code point, to the JSON output. */
375 StringBuffer output = new StringBuffer(); 555 void writeCharCode(int charCode);
376 printOn(object, output, toEncodable, indent); 556 /** Write a number to the JSON output. */
377 return output.toString(); 557 void writeNumber(num number);
378 }
379
380 static void printOn(object, StringSink output, toEncodable(object),
381 String indent) {
382 new _JsonStringifier(output, toEncodable, indent).stringifyValue(object);
383 }
384
385 static String numberToString(num x) {
386 return x.toString();
387 }
388 558
389 // ('0' + x) or ('a' + x - 10) 559 // ('0' + x) or ('a' + x - 10)
390 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x; 560 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x;
391 561
392 void escape(String s) { 562 /**
563 * Write, and suitably escape, a string's content as a JSON string literal.
564 */
565 void writeStringContent(String s) {
393 int offset = 0; 566 int offset = 0;
394 final int length = s.length; 567 final int length = s.length;
395 for (int i = 0; i < length; i++) { 568 for (int i = 0; i < length; i++) {
396 int charCode = s.codeUnitAt(i); 569 int charCode = s.codeUnitAt(i);
397 if (charCode > BACKSLASH) continue; 570 if (charCode > BACKSLASH) continue;
398 if (charCode < 32) { 571 if (charCode < 32) {
399 if (i > offset) _sink.write(s.substring(offset, i)); 572 if (i > offset) writeStringSlice(s, offset, i);
400 offset = i + 1; 573 offset = i + 1;
401 _sink.writeCharCode(BACKSLASH); 574 writeCharCode(BACKSLASH);
402 switch (charCode) { 575 switch (charCode) {
403 case BACKSPACE: 576 case BACKSPACE:
404 _sink.writeCharCode(CHAR_b); 577 writeCharCode(CHAR_b);
405 break; 578 break;
406 case TAB: 579 case TAB:
407 _sink.writeCharCode(CHAR_t); 580 writeCharCode(CHAR_t);
408 break; 581 break;
409 case NEWLINE: 582 case NEWLINE:
410 _sink.writeCharCode(CHAR_n); 583 writeCharCode(CHAR_n);
411 break; 584 break;
412 case FORM_FEED: 585 case FORM_FEED:
413 _sink.writeCharCode(CHAR_f); 586 writeCharCode(CHAR_f);
414 break; 587 break;
415 case CARRIAGE_RETURN: 588 case CARRIAGE_RETURN:
416 _sink.writeCharCode(CHAR_r); 589 writeCharCode(CHAR_r);
417 break; 590 break;
418 default: 591 default:
419 _sink.writeCharCode(CHAR_u); 592 writeCharCode(CHAR_u);
420 _sink.writeCharCode(CHAR_0); 593 writeCharCode(CHAR_0);
421 _sink.writeCharCode(CHAR_0); 594 writeCharCode(CHAR_0);
422 _sink.writeCharCode(hexDigit((charCode >> 4) & 0xf)); 595 writeCharCode(hexDigit((charCode >> 4) & 0xf));
423 _sink.writeCharCode(hexDigit(charCode & 0xf)); 596 writeCharCode(hexDigit(charCode & 0xf));
424 break; 597 break;
425 } 598 }
426 } else if (charCode == QUOTE || charCode == BACKSLASH) { 599 } else if (charCode == QUOTE || charCode == BACKSLASH) {
427 if (i > offset) _sink.write(s.substring(offset, i)); 600 if (i > offset) writeStringSlice(s, offset, i);
428 offset = i + 1; 601 offset = i + 1;
429 _sink.writeCharCode(BACKSLASH); 602 writeCharCode(BACKSLASH);
430 _sink.writeCharCode(charCode); 603 writeCharCode(charCode);
431 } 604 }
432 } 605 }
433 if (offset == 0) { 606 if (offset == 0) {
434 _sink.write(s); 607 writeString(s);
435 } else if (offset < length) { 608 } else if (offset < length) {
436 _sink.write(s.substring(offset, length)); 609 writeStringSlice(s, offset, length);
437 } 610 }
438 } 611 }
439 612
440 void checkCycle(object) { 613 /**
614 * Check if an encountered object is already being traversed.
615 *
616 * Records the object if it isn't already seen.
617 * Should have a matching call to [_removeSeen] when the object
618 * is no longer being traversed.
619 */
620 void _checkCycle(object) {
441 for (int i = 0; i < _seen.length; i++) { 621 for (int i = 0; i < _seen.length; i++) {
442 if (identical(object, _seen[i])) { 622 if (identical(object, _seen[i])) {
443 throw new JsonCyclicError(object); 623 throw new JsonCyclicError(object);
444 } 624 }
445 } 625 }
446 _seen.add(object); 626 _seen.add(object);
447 } 627 }
448 628
449 void stringifyValue(object) { 629 /**
630 * Removes object from the list of currently traversed objects.
631 *
632 * Should be called in the opposite order of the matching [_checkCycle]
633 * calls.
634 */
635 void _removeSeen(object) {
636 assert(!_seen.isEmpty);
637 assert(identical(_seen.last, object));
638 _seen.removeLast();
639 }
640
641 /**
642 * Writes an object.
643 *
644 * If the object isn't directly encodable, the [_toEncodable] function
645 * gets one chance to return a replacement which is encodable.
646 */
647 void writeObject(object) {
450 // Tries stringifying object directly. If it's not a simple value, List or 648 // Tries stringifying object directly. If it's not a simple value, List or
451 // Map, call toJson() to get a custom representation and try serializing 649 // Map, call toJson() to get a custom representation and try serializing
452 // that. 650 // that.
453 if (!stringifyJsonValue(object)) { 651 if (writeJsonValue(object)) return;
454 checkCycle(object); 652 _checkCycle(object);
455 try { 653 try {
456 var customJson = _toEncodable(object); 654 var customJson = _toEncodable(object);
457 if (!stringifyJsonValue(customJson)) { 655 if (!writeJsonValue(customJson)) {
458 throw new JsonUnsupportedObjectError(object); 656 throw new JsonUnsupportedObjectError(object);
459 }
460 _removeSeen(object);
461 } catch (e) {
462 throw new JsonUnsupportedObjectError(object, cause: e);
463 } 657 }
658 _removeSeen(object);
659 } catch (e) {
660 throw new JsonUnsupportedObjectError(object, cause: e);
464 } 661 }
465 } 662 }
466 663
467 /** 664 /**
468 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value. 665 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value.
469 * 666 *
470 * Returns true if the value is one of these types, and false if not. 667 * Returns true if the value is one of these types, and false if not.
471 * If a value is both a [List] and a [Map], it's serialized as a [List]. 668 * If a value is both a [List] and a [Map], it's serialized as a [List].
472 */ 669 */
473 bool stringifyJsonValue(object) { 670 bool writeJsonValue(object) {
474 if (object is num) { 671 if (object is num) {
475 if (!object.isFinite) return false; 672 if (!object.isFinite) return false;
476 _sink.write(numberToString(object)); 673 writeNumber(object);
477 return true; 674 return true;
478 } else if (identical(object, true)) { 675 } else if (identical(object, true)) {
479 _sink.write('true'); 676 writeString('true');
480 return true; 677 return true;
481 } else if (identical(object, false)) { 678 } else if (identical(object, false)) {
482 _sink.write('false'); 679 writeString('false');
483 return true; 680 return true;
484 } else if (object == null) { 681 } else if (object == null) {
485 _sink.write('null'); 682 writeString('null');
486 return true; 683 return true;
487 } else if (object is String) { 684 } else if (object is String) {
488 _sink.write('"'); 685 writeString('"');
489 escape(object); 686 writeStringContent(object);
490 _sink.write('"'); 687 writeString('"');
491 return true; 688 return true;
492 } else if (object is List) { 689 } else if (object is List) {
493 checkCycle(object); 690 _checkCycle(object);
494 List a = object; 691 writeList(object);
495 _sink.write('[');
496 if (a.length > 0) {
497 stringifyValue(a[0]);
498 for (int i = 1; i < a.length; i++) {
499 _sink.write(',');
500 stringifyValue(a[i]);
501 }
502 }
503 _sink.write(']');
504 _removeSeen(object); 692 _removeSeen(object);
505 return true; 693 return true;
506 } else if (object is Map) { 694 } else if (object is Map) {
507 checkCycle(object); 695 _checkCycle(object);
508 Map<String, Object> m = object; 696 writeMap(object);
509 _sink.write('{');
510 String separator = '"';
511 m.forEach((String key, value) {
512 _sink.write(separator);
513 separator = ',"';
514 escape(key);
515 _sink.write('":');
516 stringifyValue(value);
517 });
518 _sink.write('}');
519 _removeSeen(object); 697 _removeSeen(object);
520 return true; 698 return true;
521 } else { 699 } else {
522 return false; 700 return false;
523 } 701 }
524 } 702 }
525 703
526 void _removeSeen(object) { 704 /** Serializes a [List]. */
527 assert(!_seen.isEmpty); 705 void writeList(List list) {
528 assert(identical(_seen.last, object)); 706 writeString('[');
529 _seen.removeLast(); 707 if (list.length > 0) {
708 writeObject(list[0]);
709 for (int i = 1; i < list.length; i++) {
710 writeString(',');
711 writeObject(list[i]);
712 }
713 }
714 writeString(']');
715 }
716
717 /** Serializes a [Map]. */
718 void writeMap(Map<String, Object> map) {
719 writeString('{');
720 String separator = '"';
721 map.forEach((String key, value) {
722 writeString(separator);
723 separator = ',"';
724 writeStringContent(key);
725 writeString('":');
726 writeObject(value);
727 });
728 writeString('}');
530 } 729 }
531 } 730 }
532 731
533 /** 732 /**
534 * A subclass of [_JsonStringifier] which indents the contents of [List] and 733 * A modification of [_JsonStringifier] which indents the contents of [List] and
535 * [Map] objects using the specified indent value. 734 * [Map] objects using the specified indent value.
735 *
736 * Subclasses should implement [writeIndentation].
536 */ 737 */
537 class _JsonStringifierPretty extends _JsonStringifier { 738 abstract class _JsonPrettyPrintMixin implements _JsonStringifier {
739 int _indentLevel = 0;
740
741 /**
742 * Add [indentLevel] indentations to the JSON output.
743 */
744 void writeIndentation(indentLevel);
745
746 void writeList(List list) {
747 if (list.isEmpty) {
748 writeString('[]');
749 } else {
750 writeString('[\n');
751 _indentLevel++;
752 writeIndentation(_indentLevel);
753 writeObject(list[0]);
754 for (int i = 1; i < list.length; i++) {
755 writeString(',\n');
756 writeIndentation(_indentLevel);
757 writeObject(list[i]);
758 }
759 writeString('\n');
760 _indentLevel--;
761 writeIndentation(_indentLevel);
762 writeString(']');
763 }
764 }
765
766 void writeMap(Map map) {
767 if (map.isEmpty) {
768 writeString('{}');
769 } else {
770 writeString('{\n');
771 _indentLevel++;
772 bool first = true;
773 map.forEach((String key, Object value) {
774 if (!first) {
775 writeString(",\n");
776 }
777 writeIndentation(_indentLevel);
778 writeString('"');
779 writeStringContent(key);
780 writeString('": ');
781 writeObject(value);
782 first = false;
783 });
784 writeString('\n');
785 _indentLevel--;
786 writeIndentation(_indentLevel);
787 writeString('}');
788 }
789 }
790 }
791
792 /**
793 * A specialziation of [_JsonStringifier] that writes its JSON to a string.
794 */
795 class _JsonStringStringifier extends _JsonStringifier {
796 final StringSink _sink;
797
798 _JsonStringStringifier(this._sink, _toEncodable) : super(_toEncodable);
799
800 /**
801 * Convert object to a string.
802 *
803 * The [toEncodable] function is used to convert non-encodable objects
804 * to encodable ones.
805 *
806 * If [indent] is not `null`, the resulting JSON will be "pretty-printed"
807 * with newlines and indentation. The `indent` string is added as indentation
808 * for each indentation level. It should only contain valid JSON whitespace
809 * characters (space, tab, carriage return or line feed).
810 */
811 static String stringify(object, toEncodable(object), String indent) {
812 StringBuffer output = new StringBuffer();
813 printOn(object, output, toEncodable, indent);
814 return output.toString();
815 }
816
817 /**
818 * Convert object to a string, and write the result to the [output] sink.
819 *
820 * The result is written piecemally to the sink.
821 */
822 static void printOn(object, StringSink output, toEncodable(object),
823 String indent) {
824 var stringifier;
825 if (indent == null) {
826 stringifier = new _JsonStringStringifier(output, toEncodable);
827 } else {
828 stringifier =
829 new _JsonStringStringifierPretty(output, toEncodable, indent);
830 }
831 stringifier.writeObject(object);
832 }
833
834 void writeNumber(num number) {
835 _sink.write(number.toString());
836 }
837 void writeString(String string) {
838 _sink.write(string);
839 }
840 void writeStringSlice(String string, int start, int end) {
841 _sink.write(string.substring(start, end));
842 }
843 void writeCharCode(int charCode) {
844 _sink.writeCharCode(charCode);
845 }
846 }
847
848 class _JsonStringStringifierPretty extends _JsonStringStringifier
849 with _JsonPrettyPrintMixin {
538 final String _indent; 850 final String _indent;
539 851
540 int _indentLevel = 0; 852 _JsonStringStringifierPretty(StringSink sink, Function toEncodable,
541 853 this._indent)
542 _JsonStringifierPretty(_sink, _toEncodable, this._indent) 854 : super(sink, toEncodable);
543 : super._(_sink, _toEncodable); 855
544 856 void writeIndentation(int count) {
545 void _write([String value = '']) { 857 for (int i = 0; i < count; i++) writeString(_indent);
546 _sink.write(_indent * _indentLevel); 858 }
547 _sink.write(value); 859 }
548 } 860
549 861 /**
550 /** 862 * Specialization of [_JsonStringifier] that writes the JSON as UTF-8.
551 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value. 863 *
552 * 864 * The JSON text is UTF-8 encoded and written to [Uint8List] buffers.
553 * Returns true if the value is one of these types, and false if not. 865 * The buffers are then passed back to a user provided callback method.
554 * If a value is both a [List] and a [Map], it's serialized as a [List]. 866 */
555 */ 867 class _JsonUtf8Stringifier extends _JsonStringifier {
556 bool stringifyJsonValue(final object) { 868 final int bufferSize;
557 if (object is List) { 869 final Function addChunk;
558 checkCycle(object); 870 Uint8List buffer;
559 List a = object; 871 int index = 0;
560 if (a.isEmpty) { 872
561 _sink.write('[]'); 873 _JsonUtf8Stringifier(toEncodable, int bufferSize, this.addChunk)
874 : super(toEncodable),
875 this.bufferSize = bufferSize,
876 buffer = new Uint8List(bufferSize);
877
878 /**
879 * Convert [object] to UTF-8 encoded JSON.
880 *
881 * Calls [addChunk] with slices of UTF-8 code units.
882 * These will typically have size [bufferSize], but may be shorter.
883 * The buffers are not reused, so the [addChunk] call may keep and reuse
884 * the chunks.
885 *
886 * If [indent] is non-`null`, the result will be "pretty-printed" with
887 * extra newlines and indentation, using [indent] as the indentation.
888 */
889 static void stringify(Object object,
890 List<int> indent,
891 toEncodableFunction(Object o),
892 int bufferSize,
893 void addChunk(Uint8List chunk, int start, int end)) {
894 _JsonUtf8Stringifier stringifier;
895 if (indent != null) {
896 stringifier = new _JsonUtf8StringifierPretty(toEncodableFunction, indent,
897 bufferSize, addChunk);
898 } else {
899 stringifier = new _JsonUtf8Stringifier(toEncodableFunction,
900 bufferSize, addChunk);
901 }
902 stringifier.writeObject(object);
903 stringifier.flush();
904 }
905
906 /**
907 * Must be called at the end to push the last chunk to the [addChunk]
908 * callback.
909 */
910 void flush() {
911 if (index > 0) {
912 addChunk(buffer, 0, index);
913 }
914 buffer = null;
915 index = 0;
916 }
917
918 void writeNumber(num number) {
919 writeAsciiString(number.toString());
920 }
921
922 /** Write a string that is known to not have non-ASCII characters. */
923 void writeAsciiString(String string) {
924 // TODO(lrn): Optimize by copying directly into buffer instead of going
925 // through writeCharCode;
926 for (int i = 0; i < string.length; i++) {
927 int char = string.codeUnitAt(i);
928 assert(char <= 0x7f);
929 writeByte(char);
930 }
931 }
932
933 void writeString(String string) {
934 writeStringSlice(string, 0, string.length);
935 }
936
937 void writeStringSlice(String string, int start, int end) {
938 // TODO(lrn): Optimize by copying directly into buffer instead of going
939 // through writeCharCode/writeByte. Assumption is the most characters
940 // in starings are plain ASCII.
941 for (int i = start; i < end; i++) {
942 int char = string.codeUnitAt(i);
943 if (char <= 0x7f) {
944 writeByte(char);
562 } else { 945 } else {
563 _sink.writeln('['); 946 if ((char & 0xFC00) == 0xD800 && i + 1 < end) {
564 _indentLevel++; 947 // Lead surrogate.
565 _write(); 948 int nextChar = string.codeUnitAt(i + 1);
566 stringifyValue(a[0]); 949 if ((nextChar & 0xFC00) == 0xDC00) {
567 for (int i = 1; i < a.length; i++) { 950 // Tail surrogate.
568 _sink.writeln(','); 951 char = 0x10000 + ((char & 0x3ff) << 10) + (nextChar & 0x3ff);
569 _write(); 952 writeFourByteCharCode(char);
570 stringifyValue(a[i]); 953 i++;
954 continue;
955 }
571 } 956 }
572 _sink.writeln(); 957 writeMultiByteCharCode(char);
573 _indentLevel--; 958 }
574 _write(']'); 959 }
575 } 960 }
576 _seen.remove(object); 961
577 return true; 962 void writeCharCode(int charCode) {
578 } else if (object is Map) { 963 if (charCode <= 0x7f) {
579 checkCycle(object); 964 writeByte(charCode);
580 Map<String, Object> m = object; 965 return;
581 if (m.isEmpty) { 966 }
582 _sink.write('{}'); 967 writeMultiByteCharCode(charCode);
968 }
969
970 void writeMultiByteCharCode(int charCode) {
971 if (charCode <= 0x7ff) {
972 writeByte(0xC0 | (charCode >> 6));
973 writeByte(0x80 | (charCode & 0x3f));
974 return;
975 }
976 if (charCode <= 0xffff) {
977 writeByte(0xE0 | (charCode >> 12));
978 writeByte(0x80 | ((charCode >> 6) & 0x3f));
979 writeByte(0x80 | (charCode & 0x3f));
980 return;
981 }
982 writeFourByteCharCode(charCode);
983 }
984
985 void writeFourByteCharCode(int charCode) {
986 assert(charCode <= 0x10ffff);
987 writeByte(0xF0 | (charCode >> 18));
988 writeByte(0x80 | ((charCode >> 12) & 0x3f));
989 writeByte(0x80 | ((charCode >> 6) & 0x3f));
990 writeByte(0x80 | (charCode & 0x3f));
991 }
992
993 void writeByte(int byte) {
994 assert(byte <= 0xff);
995 if (index == buffer.length) {
996 addChunk(buffer, 0, index);
997 buffer = new Uint8List(bufferSize);
998 index = 0;
999 }
1000 buffer[index++] = byte;
1001 }
1002 }
1003
1004 /**
1005 * Pretty-printing version of [_JsonUtf8Stringifier].
1006 */
1007 class _JsonUtf8StringifierPretty extends _JsonUtf8Stringifier
1008 with _JsonPrettyPrintMixin {
1009 final List<int> indent;
1010 _JsonUtf8StringifierPretty(toEncodableFunction, this.indent,
1011 bufferSize, addChunk)
1012 : super(toEncodableFunction, bufferSize, addChunk);
1013
1014 void writeIndentation(int count) {
1015 List<int> indent = this.indent;
1016 int indentLength = indent.length;
1017 if (indentLength == 1) {
1018 int char = indent[0];
1019 while (count > 0) {
1020 writeByte(char);
1021 count -= 1;
1022 }
1023 return;
1024 }
1025 while (count > 0) {
1026 count--;
1027 int end = index + indentLength;
1028 if (end <= buffer.length) {
1029 buffer.setRange(index, end, indent);
1030 index = end;
583 } else { 1031 } else {
584 _sink.write('{'); 1032 for (int i = 0; i < indentLength; i++) {
585 _sink.writeln(); 1033 writeByte(indent[i]);
586 _indentLevel++; 1034 }
587 bool first = true; 1035 }
588 m.forEach((String key, Object value) { 1036 }
589 if (!first) { 1037 }
590 _sink.writeln(','); 1038 }
591 }
592 _write('"');
593 escape(key);
594 _sink.write('": ');
595 stringifyValue(value);
596 first = false;
597 });
598 _sink.writeln();
599 _indentLevel--;
600 _write('}');
601 }
602 _seen.remove(object);
603 return true;
604 }
605 return super.stringifyJsonValue(object);
606 }
607 }
OLDNEW
« no previous file with comments | « sdk/lib/_internal/compiler/js_lib/convert_patch.dart ('k') | sdk/lib/convert/utf.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698