Index: sdk/lib/convert/json.dart |
diff --git a/sdk/lib/convert/json.dart b/sdk/lib/convert/json.dart |
index 07de8f4caf45eca02a8854464696ca15fae2efe9..8c4d53ef2f76192c49efb47b6bcc87d4bf68b4f5 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> { |
+ /** |
+ * The value appended for each level the JSON output is indented. |
Lasse Reichstein Nielsen
2014/04/05 19:34:43
The string used for indentation.
When generating
kevmoo
2014/04/05 20:08:58
Done.
|
+ * |
+ * If `null`, the output is encoded in a single line. |
Lasse Reichstein Nielsen
2014/04/05 19:34:43
in -> as
kevmoo
2014/04/05 20:08:58
Done.
|
+ */ |
+ final String indent; |
+ |
final _toEncodableFunction; |
/** |
@@ -172,6 +179,25 @@ class JsonEncoder extends Converter<Object, String> { |
* the object. |
*/ |
const JsonEncoder([Object toEncodable(Object nonSerializable)]) |
+ : this.indent = null, |
+ this._toEncodableFunction = toEncodable; |
+ |
+ /** |
+ * Creates a JSON encoder with each output level having a specified [indent]. |
Lasse Reichstein Nielsen
2014/04/05 19:34:43
"output level" is not defined anywhere.
How about:
kevmoo
2014/04/05 20:08:58
Done.
|
+ * |
+ * 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(this.indent, |
+ [Object toEncodable(Object nonSerializable)]) |
: this._toEncodableFunction = toEncodable; |
/** |
@@ -202,7 +228,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 +243,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 +256,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 String _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 +276,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 +380,25 @@ class _JsonStringifier { |
final StringSink _sink; |
final List _seen; |
- _JsonStringifier(this._sink, this._toEncodable) |
+ factory _JsonStringifier(StringSink sink, Function toEncodable, |
+ String 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), String 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), |
+ String indent) { |
+ new _JsonStringifier(output, toEncodable, indent).stringifyValue(object); |
} |
static String numberToString(num x) { |
@@ -515,3 +548,82 @@ class _JsonStringifier { |
_seen.removeLast(); |
} |
} |
+ |
+ |
+class _JsonStringifierPretty extends _JsonStringifier { |
Lasse Reichstein Nielsen
2014/04/05 19:34:43
Add a class comment.
kevmoo
2014/04/05 20:08:58
Done.
|
+ |
+ final String _indent; |
+ |
+ int _indentLevel = 0; |
+ |
+ _JsonStringifierPretty(_sink, _toEncodable, this._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); |
+ } |
+} |