Chromium Code Reviews| Index: sdk/lib/convert/json.dart |
| diff --git a/sdk/lib/convert/json.dart b/sdk/lib/convert/json.dart |
| index 07de8f4caf45eca02a8854464696ca15fae2efe9..5b3474e9d2d836ef1e5b0547358b1362308f3e24 100644 |
| --- a/sdk/lib/convert/json.dart |
| +++ b/sdk/lib/convert/json.dart |
| @@ -157,6 +157,13 @@ class JsonCodec extends Codec<Object, String> { |
| * This class converts JSON objects to strings. |
| */ |
| class JsonEncoder extends Converter<Object, String> { |
| + /** |
| + * How many spaces each level of the JSON output is indented. |
| + * |
| + * If `null`, the output is encoded in a single line. |
| + */ |
| + final int indent; |
|
Lasse Reichstein Nielsen
2014/04/03 18:18:17
Consider letting "indent" be a string which will b
|
| + |
| final _toEncodableFunction; |
| /** |
| @@ -172,7 +179,27 @@ class JsonEncoder extends Converter<Object, String> { |
| * the object. |
| */ |
| const JsonEncoder([Object toEncodable(Object nonSerializable)]) |
| - : this._toEncodableFunction = toEncodable; |
| + : this.indent = null, |
| + this._toEncodableFunction = toEncodable; |
| + |
| + /** |
| + * Creates a JSON encoder with each output level having a specified [indent]. |
| + * |
| + * An encoder with a `null` [indent] encodes objects onto a single line. |
| + * |
| + * The JSON encoder handles numbers, strings, booleans, null, lists and |
| + * maps directly. |
| + * |
| + * Any other object is attempted converted by [toEncodable] to an |
| + * object that is of one of the convertible types. |
| + * |
| + * If [toEncodable] is omitted, it defaults to calling `.toJson()` on |
| + * the object. |
| + */ |
| + const JsonEncoder.withIndent(int indent, |
| + [Object toEncodable(Object nonSerializable)]) |
| + : this.indent = indent != null && indent < 0 ? 0 : indent, |
| + this._toEncodableFunction = toEncodable; |
| /** |
| * Converts the given object [o] to its JSON representation. |
| @@ -202,7 +229,7 @@ class JsonEncoder extends Converter<Object, String> { |
| * serialized, the new values may or may not be reflected in the result. |
| */ |
| String convert(Object o) => |
| - _JsonStringifier.stringify(o, _toEncodableFunction); |
| + _JsonStringifier.stringify(o, _toEncodableFunction, indent); |
| /** |
| * Starts a chunked conversion. |
| @@ -217,7 +244,7 @@ class JsonEncoder extends Converter<Object, String> { |
| if (sink is! StringConversionSink) { |
| sink = new StringConversionSink.from(sink); |
| } |
| - return new _JsonEncoderSink(sink, _toEncodableFunction); |
| + return new _JsonEncoderSink(sink, _toEncodableFunction, indent); |
| } |
| // Override the base-classes bind, to provide a better type. |
| @@ -230,11 +257,12 @@ class JsonEncoder extends Converter<Object, String> { |
| * The sink only accepts one value, but will produce output in a chunked way. |
| */ |
| class _JsonEncoderSink extends ChunkedConversionSink<Object> { |
| + final int _indent; |
| final Function _toEncodableFunction; |
| final StringConversionSink _sink; |
| bool _isDone = false; |
| - _JsonEncoderSink(this._sink, this._toEncodableFunction); |
| + _JsonEncoderSink(this._sink, this._toEncodableFunction, this._indent); |
| /** |
| * Encodes the given object [o]. |
| @@ -249,7 +277,7 @@ class _JsonEncoderSink extends ChunkedConversionSink<Object> { |
| } |
| _isDone = true; |
| ClosableStringSink stringSink = _sink.asStringSink(); |
| - _JsonStringifier.printOn(o, stringSink, _toEncodableFunction); |
| + _JsonStringifier.printOn(o, stringSink, _toEncodableFunction, _indent); |
| stringSink.close(); |
| } |
| @@ -353,19 +381,24 @@ class _JsonStringifier { |
| final StringSink _sink; |
| final List _seen; |
| - _JsonStringifier(this._sink, this._toEncodable) |
| + factory _JsonStringifier(StringSink sink, Function toEncodable, int indent) { |
| + if (indent == null) return new _JsonStringifier._(sink, toEncodable); |
| + return new _JsonStringifierPretty(sink, toEncodable, indent); |
| + } |
| + |
| + _JsonStringifier._(this._sink, this._toEncodable) |
| : this._seen = new List(); |
| - static String stringify(object, toEncodable(object)) { |
| + static String stringify(object, toEncodable(object), int indent) { |
| if (toEncodable == null) toEncodable = _defaultToEncodable; |
| StringBuffer output = new StringBuffer(); |
| - printOn(object, output, toEncodable); |
| + printOn(object, output, toEncodable, indent); |
| return output.toString(); |
| } |
| - static void printOn(object, StringSink output, toEncodable(object)) { |
| - _JsonStringifier stringifier = new _JsonStringifier(output, toEncodable); |
| - stringifier.stringifyValue(object); |
| + static void printOn(object, StringSink output, toEncodable(object), |
| + int indent) { |
| + new _JsonStringifier(output, toEncodable, indent).stringifyValue(object); |
| } |
| static String numberToString(num x) { |
| @@ -515,3 +548,83 @@ class _JsonStringifier { |
| _seen.removeLast(); |
| } |
| } |
| + |
| + |
| +class _JsonStringifierPretty extends _JsonStringifier { |
| + |
| + final String _indent; |
| + |
| + int _indentLevel = 0; |
| + |
| + _JsonStringifierPretty(_sink, _toEncodable, int indent) |
| + : this._indent = _getIndent(indent), |
| + super._(_sink, _toEncodable); |
| + |
| + static String _getIndent(int indent) { |
| + if (indent < 0) throw new ArgumentError('indent cannot be negative'); |
| + return ' ' * indent; |
| + } |
| + |
| + void _write([String value = '']) { |
| + _sink.write(_indent * _indentLevel); |
| + _sink.write(value); |
| + } |
| + |
| + /** |
| + * Serializes a [num], [String], [bool], [Null], [List] or [Map] value. |
| + * |
| + * Returns true if the value is one of these types, and false if not. |
| + * If a value is both a [List] and a [Map], it's serialized as a [List]. |
| + */ |
| + bool stringifyJsonValue(final object) { |
| + if (object is List) { |
| + checkCycle(object); |
| + List a = object; |
| + if (a.isEmpty) { |
| + _sink.write('[]'); |
| + } else { |
| + _sink.writeln('['); |
| + _indentLevel++; |
| + _write(); |
| + stringifyValue(a[0]); |
| + for (int i = 1; i < a.length; i++) { |
| + _sink.writeln(','); |
| + _write(); |
| + stringifyValue(a[i]); |
| + } |
| + _sink.writeln(); |
| + _indentLevel--; |
| + _write(']'); |
| + } |
| + _seen.remove(object); |
| + return true; |
| + } else if (object is Map) { |
| + checkCycle(object); |
| + Map<String, Object> m = object; |
| + if (m.isEmpty) { |
| + _sink.write('{}'); |
| + } else { |
| + _sink.write('{'); |
| + _sink.writeln(); |
| + _indentLevel++; |
| + bool first = true; |
| + m.forEach((String key, Object value) { |
| + if (!first) { |
| + _sink.writeln(','); |
| + } |
| + _write('"'); |
| + escape(key); |
| + _sink.write('": '); |
| + stringifyValue(value); |
| + first = false; |
| + }); |
| + _sink.writeln(); |
| + _indentLevel--; |
| + _write('}'); |
| + } |
| + _seen.remove(object); |
| + return true; |
| + } |
| + return super.stringifyJsonValue(object); |
| + } |
| +} |