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

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