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

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

Issue 224093007: dart:convert: support indented output w/ JsonEncoder (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 8 months 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 | « no previous file | tests/lib/convert/json_pretty_test.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 139 matching lines...) Expand 10 before | Expand all | Expand 10 after
150 JsonDecoder get decoder { 150 JsonDecoder get decoder {
151 if (_reviver == null) return const JsonDecoder(); 151 if (_reviver == null) return const JsonDecoder();
152 return new JsonDecoder(_reviver); 152 return new JsonDecoder(_reviver);
153 } 153 }
154 } 154 }
155 155
156 /** 156 /**
157 * This class converts JSON objects to strings. 157 * This class converts JSON objects to strings.
158 */ 158 */
159 class JsonEncoder extends Converter<Object, String> { 159 class JsonEncoder extends Converter<Object, String> {
160 /**
161 * How many spaces each level of the JSON output is indented.
162 *
163 * If `null`, the output is encoded in a single line.
164 */
165 final int indent;
Lasse Reichstein Nielsen 2014/04/03 18:18:17 Consider letting "indent" be a string which will b
166
160 final _toEncodableFunction; 167 final _toEncodableFunction;
161 168
162 /** 169 /**
163 * Creates a JSON encoder. 170 * Creates a JSON encoder.
164 * 171 *
165 * The JSON encoder handles numbers, strings, booleans, null, lists and 172 * The JSON encoder handles numbers, strings, booleans, null, lists and
166 * maps directly. 173 * maps directly.
167 * 174 *
168 * Any other object is attempted converted by [toEncodable] to an 175 * Any other object is attempted converted by [toEncodable] to an
169 * object that is of one of the convertible types. 176 * object that is of one of the convertible types.
170 * 177 *
171 * If [toEncodable] is omitted, it defaults to calling `.toJson()` on 178 * If [toEncodable] is omitted, it defaults to calling `.toJson()` on
172 * the object. 179 * the object.
173 */ 180 */
174 const JsonEncoder([Object toEncodable(Object nonSerializable)]) 181 const JsonEncoder([Object toEncodable(Object nonSerializable)])
175 : this._toEncodableFunction = toEncodable; 182 : this.indent = null,
183 this._toEncodableFunction = toEncodable;
184
185 /**
186 * Creates a JSON encoder with each output level having a specified [indent].
187 *
188 * An encoder with a `null` [indent] encodes objects onto a single line.
189 *
190 * The JSON encoder handles numbers, strings, booleans, null, lists and
191 * maps directly.
192 *
193 * Any other object is attempted converted by [toEncodable] to an
194 * object that is of one of the convertible types.
195 *
196 * If [toEncodable] is omitted, it defaults to calling `.toJson()` on
197 * the object.
198 */
199 const JsonEncoder.withIndent(int indent,
200 [Object toEncodable(Object nonSerializable)])
201 : this.indent = indent != null && indent < 0 ? 0 : indent,
202 this._toEncodableFunction = toEncodable;
176 203
177 /** 204 /**
178 * Converts the given object [o] to its JSON representation. 205 * Converts the given object [o] to its JSON representation.
179 * 206 *
180 * Directly serializable values are [num], [String], [bool], and [Null], as 207 * Directly serializable values are [num], [String], [bool], and [Null], as
181 * well as some [List] and [Map] values. 208 * well as some [List] and [Map] values.
182 * For [List], the elements must all be serializable. 209 * For [List], the elements must all be serializable.
183 * For [Map], the keys must be [String] and the values must be serializable. 210 * For [Map], the keys must be [String] and the values must be serializable.
184 * 211 *
185 * If a value is any other type is attempted serialized, the conversion 212 * If a value is any other type is attempted serialized, the conversion
186 * function provided in the constructor is invoked with the object as argument 213 * function provided in the constructor is invoked with the object as argument
187 * and the result, which must be a directly serializable value, 214 * and the result, which must be a directly serializable value,
188 * is serialized instead of the original value. 215 * is serialized instead of the original value.
189 * 216 *
190 * If the conversion throws, or returns a value that is not directly 217 * If the conversion throws, or returns a value that is not directly
191 * serializable, a [JsonUnsupportedObjectError] exception is thrown. 218 * serializable, a [JsonUnsupportedObjectError] exception is thrown.
192 * If the call throws, the error is caught and stored in the 219 * If the call throws, the error is caught and stored in the
193 * [JsonUnsupportedObjectError]'s [:cause:] field. 220 * [JsonUnsupportedObjectError]'s [:cause:] field.
194 * 221 *
195 * If a [List] or [Map] contains a reference to itself, directly or through 222 * If a [List] or [Map] contains a reference to itself, directly or through
196 * other lists or maps, it cannot be serialized and a [JsonCyclicError] is 223 * other lists or maps, it cannot be serialized and a [JsonCyclicError] is
197 * thrown. 224 * thrown.
198 * 225 *
199 * Json Objects should not change during serialization. 226 * Json Objects should not change during serialization.
200 * If an object is serialized more than once, [stringify] is allowed to cache 227 * If an object is serialized more than once, [stringify] is allowed to cache
201 * the JSON text for it. I.e., if an object changes after it is first 228 * the JSON text for it. I.e., if an object changes after it is first
202 * serialized, the new values may or may not be reflected in the result. 229 * serialized, the new values may or may not be reflected in the result.
203 */ 230 */
204 String convert(Object o) => 231 String convert(Object o) =>
205 _JsonStringifier.stringify(o, _toEncodableFunction); 232 _JsonStringifier.stringify(o, _toEncodableFunction, indent);
206 233
207 /** 234 /**
208 * Starts a chunked conversion. 235 * Starts a chunked conversion.
209 * 236 *
210 * The converter works more efficiently if the given [sink] is a 237 * The converter works more efficiently if the given [sink] is a
211 * [StringConversionSink]. 238 * [StringConversionSink].
212 * 239 *
213 * Returns a chunked-conversion sink that accepts at most one object. It is 240 * Returns a chunked-conversion sink that accepts at most one object. It is
214 * an error to invoke `add` more than once on the returned sink. 241 * an error to invoke `add` more than once on the returned sink.
215 */ 242 */
216 ChunkedConversionSink<Object> startChunkedConversion(Sink<String> sink) { 243 ChunkedConversionSink<Object> startChunkedConversion(Sink<String> sink) {
217 if (sink is! StringConversionSink) { 244 if (sink is! StringConversionSink) {
218 sink = new StringConversionSink.from(sink); 245 sink = new StringConversionSink.from(sink);
219 } 246 }
220 return new _JsonEncoderSink(sink, _toEncodableFunction); 247 return new _JsonEncoderSink(sink, _toEncodableFunction, indent);
221 } 248 }
222 249
223 // Override the base-classes bind, to provide a better type. 250 // Override the base-classes bind, to provide a better type.
224 Stream<String> bind(Stream<Object> stream) => super.bind(stream); 251 Stream<String> bind(Stream<Object> stream) => super.bind(stream);
225 } 252 }
226 253
227 /** 254 /**
228 * Implements the chunked conversion from object to its JSON representation. 255 * Implements the chunked conversion from object to its JSON representation.
229 * 256 *
230 * The sink only accepts one value, but will produce output in a chunked way. 257 * The sink only accepts one value, but will produce output in a chunked way.
231 */ 258 */
232 class _JsonEncoderSink extends ChunkedConversionSink<Object> { 259 class _JsonEncoderSink extends ChunkedConversionSink<Object> {
260 final int _indent;
233 final Function _toEncodableFunction; 261 final Function _toEncodableFunction;
234 final StringConversionSink _sink; 262 final StringConversionSink _sink;
235 bool _isDone = false; 263 bool _isDone = false;
236 264
237 _JsonEncoderSink(this._sink, this._toEncodableFunction); 265 _JsonEncoderSink(this._sink, this._toEncodableFunction, this._indent);
238 266
239 /** 267 /**
240 * Encodes the given object [o]. 268 * Encodes the given object [o].
241 * 269 *
242 * It is an error to invoke this method more than once on any instance. While 270 * It is an error to invoke this method more than once on any instance. While
243 * this makes the input effectly non-chunked the output will be generated in 271 * this makes the input effectly non-chunked the output will be generated in
244 * a chunked way. 272 * a chunked way.
245 */ 273 */
246 void add(Object o) { 274 void add(Object o) {
247 if (_isDone) { 275 if (_isDone) {
248 throw new StateError("Only one call to add allowed"); 276 throw new StateError("Only one call to add allowed");
249 } 277 }
250 _isDone = true; 278 _isDone = true;
251 ClosableStringSink stringSink = _sink.asStringSink(); 279 ClosableStringSink stringSink = _sink.asStringSink();
252 _JsonStringifier.printOn(o, stringSink, _toEncodableFunction); 280 _JsonStringifier.printOn(o, stringSink, _toEncodableFunction, _indent);
253 stringSink.close(); 281 stringSink.close();
254 } 282 }
255 283
256 void close() { /* do nothing */ } 284 void close() { /* do nothing */ }
257 } 285 }
258 286
259 /** 287 /**
260 * This class parses JSON strings and builds the corresponding objects. 288 * This class parses JSON strings and builds the corresponding objects.
261 */ 289 */
262 class JsonDecoder extends Converter<String, Object> { 290 class JsonDecoder extends Converter<String, Object> {
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
346 static const int CHAR_f = 0x66; 374 static const int CHAR_f = 0x66;
347 static const int CHAR_n = 0x6e; 375 static const int CHAR_n = 0x6e;
348 static const int CHAR_r = 0x72; 376 static const int CHAR_r = 0x72;
349 static const int CHAR_t = 0x74; 377 static const int CHAR_t = 0x74;
350 static const int CHAR_u = 0x75; 378 static const int CHAR_u = 0x75;
351 379
352 final Function _toEncodable; 380 final Function _toEncodable;
353 final StringSink _sink; 381 final StringSink _sink;
354 final List _seen; 382 final List _seen;
355 383
356 _JsonStringifier(this._sink, this._toEncodable) 384 factory _JsonStringifier(StringSink sink, Function toEncodable, int indent) {
385 if (indent == null) return new _JsonStringifier._(sink, toEncodable);
386 return new _JsonStringifierPretty(sink, toEncodable, indent);
387 }
388
389 _JsonStringifier._(this._sink, this._toEncodable)
357 : this._seen = new List(); 390 : this._seen = new List();
358 391
359 static String stringify(object, toEncodable(object)) { 392 static String stringify(object, toEncodable(object), int indent) {
360 if (toEncodable == null) toEncodable = _defaultToEncodable; 393 if (toEncodable == null) toEncodable = _defaultToEncodable;
361 StringBuffer output = new StringBuffer(); 394 StringBuffer output = new StringBuffer();
362 printOn(object, output, toEncodable); 395 printOn(object, output, toEncodable, indent);
363 return output.toString(); 396 return output.toString();
364 } 397 }
365 398
366 static void printOn(object, StringSink output, toEncodable(object)) { 399 static void printOn(object, StringSink output, toEncodable(object),
367 _JsonStringifier stringifier = new _JsonStringifier(output, toEncodable); 400 int indent) {
368 stringifier.stringifyValue(object); 401 new _JsonStringifier(output, toEncodable, indent).stringifyValue(object);
369 } 402 }
370 403
371 static String numberToString(num x) { 404 static String numberToString(num x) {
372 return x.toString(); 405 return x.toString();
373 } 406 }
374 407
375 // ('0' + x) or ('a' + x - 10) 408 // ('0' + x) or ('a' + x - 10)
376 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x; 409 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x;
377 410
378 void escape(String s) { 411 void escape(String s) {
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after
508 return false; 541 return false;
509 } 542 }
510 } 543 }
511 544
512 void _removeSeen(object) { 545 void _removeSeen(object) {
513 assert(!_seen.isEmpty); 546 assert(!_seen.isEmpty);
514 assert(identical(_seen.last, object)); 547 assert(identical(_seen.last, object));
515 _seen.removeLast(); 548 _seen.removeLast();
516 } 549 }
517 } 550 }
551
552
553 class _JsonStringifierPretty extends _JsonStringifier {
554
555 final String _indent;
556
557 int _indentLevel = 0;
558
559 _JsonStringifierPretty(_sink, _toEncodable, int indent)
560 : this._indent = _getIndent(indent),
561 super._(_sink, _toEncodable);
562
563 static String _getIndent(int indent) {
564 if (indent < 0) throw new ArgumentError('indent cannot be negative');
565 return ' ' * indent;
566 }
567
568 void _write([String value = '']) {
569 _sink.write(_indent * _indentLevel);
570 _sink.write(value);
571 }
572
573 /**
574 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value.
575 *
576 * Returns true if the value is one of these types, and false if not.
577 * If a value is both a [List] and a [Map], it's serialized as a [List].
578 */
579 bool stringifyJsonValue(final object) {
580 if (object is List) {
581 checkCycle(object);
582 List a = object;
583 if (a.isEmpty) {
584 _sink.write('[]');
585 } else {
586 _sink.writeln('[');
587 _indentLevel++;
588 _write();
589 stringifyValue(a[0]);
590 for (int i = 1; i < a.length; i++) {
591 _sink.writeln(',');
592 _write();
593 stringifyValue(a[i]);
594 }
595 _sink.writeln();
596 _indentLevel--;
597 _write(']');
598 }
599 _seen.remove(object);
600 return true;
601 } else if (object is Map) {
602 checkCycle(object);
603 Map<String, Object> m = object;
604 if (m.isEmpty) {
605 _sink.write('{}');
606 } else {
607 _sink.write('{');
608 _sink.writeln();
609 _indentLevel++;
610 bool first = true;
611 m.forEach((String key, Object value) {
612 if (!first) {
613 _sink.writeln(',');
614 }
615 _write('"');
616 escape(key);
617 _sink.write('": ');
618 stringifyValue(value);
619 first = false;
620 });
621 _sink.writeln();
622 _indentLevel--;
623 _write('}');
624 }
625 _seen.remove(object);
626 return true;
627 }
628 return super.stringifyJsonValue(object);
629 }
630 }
OLDNEW
« no previous file with comments | « no previous file | tests/lib/convert/json_pretty_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698