commit d34a3c66b36e3afc8ea7f275b449033fa7a1b4bc (tree)
parent 28b7306a31f52f53e7936018c01c0e8d24ebf6ea
Author: Andrew Kelley <andrew@ziglang.org>
Date: Tue, 31 Mar 2020 12:07:25 -0400
Merge pull request #4543 from daurnimator/cleanup-json
std.json improvements
Diffstat:
2 files changed, 354 insertions(+), 150 deletions(-)
diff --git a/lib/std/json.zig b/lib/std/json.zig
@@ -1233,42 +1233,119 @@ pub const Value = union(enum) {
Array: Array,
Object: ObjectMap,
+ pub fn jsonStringify(
+ value: @This(),
+ options: StringifyOptions,
+ out_stream: var,
+ ) @TypeOf(out_stream).Error!void {
+ switch (value) {
+ .Null => try stringify(null, options, out_stream),
+ .Bool => |inner| try stringify(inner, options, out_stream),
+ .Integer => |inner| try stringify(inner, options, out_stream),
+ .Float => |inner| try stringify(inner, options, out_stream),
+ .String => |inner| try stringify(inner, options, out_stream),
+ .Array => |inner| try stringify(inner.span(), options, out_stream),
+ .Object => |inner| {
+ try out_stream.writeByte('{');
+ var field_output = false;
+ var child_options = options;
+ if (child_options.whitespace) |*child_whitespace| {
+ child_whitespace.indent_level += 1;
+ }
+ var it = inner.iterator();
+ while (it.next()) |entry| {
+ if (!field_output) {
+ field_output = true;
+ } else {
+ try out_stream.writeByte(',');
+ }
+ if (child_options.whitespace) |child_whitespace| {
+ try out_stream.writeByte('\n');
+ try child_whitespace.outputIndent(out_stream);
+ }
+
+ try stringify(entry.key, options, out_stream);
+ try out_stream.writeByte(':');
+ if (child_options.whitespace) |child_whitespace| {
+ if (child_whitespace.separator) {
+ try out_stream.writeByte(' ');
+ }
+ }
+ try stringify(entry.value, child_options, out_stream);
+ }
+ if (field_output) {
+ if (options.whitespace) |whitespace| {
+ try out_stream.writeByte('\n');
+ try whitespace.outputIndent(out_stream);
+ }
+ }
+ try out_stream.writeByte('}');
+ },
+ }
+ }
+
pub fn dump(self: Value) void {
var held = std.debug.getStderrMutex().acquire();
defer held.release();
const stderr = std.debug.getStderrStream();
- self.dumpStream(stderr, 1024) catch return;
+ std.json.stringify(self, std.json.StringifyOptions{ .whitespace = null }, stderr) catch return;
}
+};
- pub fn dumpIndent(self: Value, comptime indent: usize) void {
- if (indent == 0) {
- self.dump();
- } else {
- var held = std.debug.getStderrMutex().acquire();
- defer held.release();
-
- const stderr = std.debug.getStderrStream();
- self.dumpStreamIndent(indent, stderr, 1024) catch return;
- }
+test "Value.jsonStringify" {
+ {
+ var buffer: [10]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&buffer);
+ try @as(Value, .Null).jsonStringify(.{}, fbs.outStream());
+ testing.expectEqualSlices(u8, fbs.getWritten(), "null");
}
-
- pub fn dumpStream(self: @This(), stream: var, comptime max_depth: usize) !void {
- var w = std.json.WriteStream(@TypeOf(stream).Child, max_depth).init(stream);
- w.newline = "";
- w.one_indent = "";
- w.space = "";
- try w.emitJson(self);
+ {
+ var buffer: [10]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&buffer);
+ try (Value{ .Bool = true }).jsonStringify(.{}, fbs.outStream());
+ testing.expectEqualSlices(u8, fbs.getWritten(), "true");
}
-
- pub fn dumpStreamIndent(self: @This(), comptime indent: usize, stream: var, comptime max_depth: usize) !void {
- var one_indent = " " ** indent;
-
- var w = std.json.WriteStream(@TypeOf(stream).Child, max_depth).init(stream);
- w.one_indent = one_indent;
- try w.emitJson(self);
+ {
+ var buffer: [10]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&buffer);
+ try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.outStream());
+ testing.expectEqualSlices(u8, fbs.getWritten(), "42");
}
-};
+ {
+ var buffer: [10]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&buffer);
+ try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.outStream());
+ testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01");
+ }
+ {
+ var buffer: [10]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&buffer);
+ try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.outStream());
+ testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\"");
+ }
+ {
+ var buffer: [10]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&buffer);
+ try (Value{
+ .Array = Array.fromOwnedSlice(undefined, &[_]Value{
+ .{ .Integer = 1 },
+ .{ .Integer = 2 },
+ .{ .Integer = 3 },
+ }),
+ }).jsonStringify(.{}, fbs.outStream());
+ testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]");
+ }
+ {
+ var buffer: [10]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&buffer);
+ var obj = ObjectMap.init(testing.allocator);
+ defer obj.deinit();
+ try obj.putNoClobber("a", .{ .String = "b" });
+ try (Value{ .Object = obj }).jsonStringify(.{}, fbs.outStream());
+ testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}");
+ }
+}
pub const ParseOptions = struct {
allocator: ?*Allocator = null,
@@ -2241,11 +2318,86 @@ test "string copy option" {
}
pub const StringifyOptions = struct {
- // TODO: indentation options?
- // TODO: make escaping '/' in strings optional?
- // TODO: allow picking if []u8 is string or array?
+ pub const Whitespace = struct {
+ /// How many indentation levels deep are we?
+ indent_level: usize = 0,
+
+ pub const Indentation = union(enum) {
+ Space: u8,
+ Tab: void,
+ };
+
+ /// What character(s) should be used for indentation?
+ indent: Indentation = Indentation{ .Space = 4 },
+
+ fn outputIndent(
+ whitespace: @This(),
+ out_stream: var,
+ ) @TypeOf(out_stream).Error!void {
+ var char: u8 = undefined;
+ var n_chars: usize = undefined;
+ switch (whitespace.indent) {
+ .Space => |n_spaces| {
+ char = ' ';
+ n_chars = n_spaces;
+ },
+ .Tab => {
+ char = '\t';
+ n_chars = 1;
+ },
+ }
+ n_chars *= whitespace.indent_level;
+ try out_stream.writeByteNTimes(char, n_chars);
+ }
+
+ /// After a colon, should whitespace be inserted?
+ separator: bool = true,
+ };
+
+ /// Controls the whitespace emitted
+ whitespace: ?Whitespace = null,
+
+ /// Should []u8 be serialised as a string? or an array?
+ pub const StringOptions = union(enum) {
+ Array,
+
+ /// String output options
+ const StringOutputOptions = struct {
+ /// Should '/' be escaped in strings?
+ escape_solidus: bool = false,
+
+ /// Should unicode characters be escaped in strings?
+ escape_unicode: bool = false,
+ };
+ String: StringOutputOptions,
+ };
+
+ string: StringOptions = StringOptions{ .String = .{} },
};
+fn outputUnicodeEscape(
+ codepoint: u21,
+ out_stream: var,
+) !void {
+ if (codepoint <= 0xFFFF) {
+ // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),
+ // then it may be represented as a six-character sequence: a reverse solidus, followed
+ // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.
+ try out_stream.writeAll("\\u");
+ try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
+ } else {
+ assert(codepoint <= 0x10FFFF);
+ // To escape an extended character that is not in the Basic Multilingual Plane,
+ // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair.
+ const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800;
+ const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00;
+ try out_stream.writeAll("\\u");
+ try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
+ try out_stream.writeAll("\\u");
+ try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
+ }
+}
+
pub fn stringify(
value: var,
options: StringifyOptions,
@@ -2262,11 +2414,14 @@ pub fn stringify(
.Bool => {
return out_stream.writeAll(if (value) "true" else "false");
},
+ .Null => {
+ return out_stream.writeAll("null");
+ },
.Optional => {
if (value) |payload| {
return try stringify(payload, options, out_stream);
} else {
- return out_stream.writeAll("null");
+ return try stringify(null, options, out_stream);
}
},
.Enum => {
@@ -2297,8 +2452,12 @@ pub fn stringify(
return value.jsonStringify(options, out_stream);
}
- try out_stream.writeAll("{");
+ try out_stream.writeByte('{');
comptime var field_output = false;
+ var child_options = options;
+ if (child_options.whitespace) |*child_whitespace| {
+ child_whitespace.indent_level += 1;
+ }
inline for (S.fields) |Field, field_i| {
// don't include void fields
if (Field.field_type == void) continue;
@@ -2306,14 +2465,28 @@ pub fn stringify(
if (!field_output) {
field_output = true;
} else {
- try out_stream.writeAll(",");
+ try out_stream.writeByte(',');
+ }
+ if (child_options.whitespace) |child_whitespace| {
+ try out_stream.writeByte('\n');
+ try child_whitespace.outputIndent(out_stream);
}
-
try stringify(Field.name, options, out_stream);
- try out_stream.writeAll(":");
- try stringify(@field(value, Field.name), options, out_stream);
+ try out_stream.writeByte(':');
+ if (child_options.whitespace) |child_whitespace| {
+ if (child_whitespace.separator) {
+ try out_stream.writeByte(' ');
+ }
+ }
+ try stringify(@field(value, Field.name), child_options, out_stream);
+ }
+ if (field_output) {
+ if (options.whitespace) |whitespace| {
+ try out_stream.writeByte('\n');
+ try whitespace.outputIndent(out_stream);
+ }
}
- try out_stream.writeAll("}");
+ try out_stream.writeByte('}');
return;
},
.Pointer => |ptr_info| switch (ptr_info.size) {
@@ -2329,17 +2502,26 @@ pub fn stringify(
},
// TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)
.Slice => {
- if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) {
- try out_stream.writeAll("\"");
+ if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(value)) {
+ try out_stream.writeByte('\"');
var i: usize = 0;
while (i < value.len) : (i += 1) {
switch (value[i]) {
- // normal ascii characters
- 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try out_stream.writeAll(value[i .. i + 1]),
- // control characters with short escapes
+ // normal ascii character
+ 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try out_stream.writeByte(c),
+ // only 2 characters that *must* be escaped
'\\' => try out_stream.writeAll("\\\\"),
'\"' => try out_stream.writeAll("\\\""),
- '/' => try out_stream.writeAll("\\/"),
+ // solidus is optional to escape
+ '/' => {
+ if (options.string.String.escape_solidus) {
+ try out_stream.writeAll("\\/");
+ } else {
+ try out_stream.writeByte('\\');
+ }
+ },
+ // control characters with short escapes
+ // TODO: option to switch between unicode and 'short' forms?
0x8 => try out_stream.writeAll("\\b"),
0xC => try out_stream.writeAll("\\f"),
'\n' => try out_stream.writeAll("\\n"),
@@ -2347,39 +2529,43 @@ pub fn stringify(
'\t' => try out_stream.writeAll("\\t"),
else => {
const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable;
- const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;
- if (codepoint <= 0xFFFF) {
- // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),
- // then it may be represented as a six-character sequence: a reverse solidus, followed
- // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.
- try out_stream.writeAll("\\u");
- try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
+ // control characters (only things left with 1 byte length) should always be printed as unicode escapes
+ if (ulen == 1 or options.string.String.escape_unicode) {
+ const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;
+ try outputUnicodeEscape(codepoint, out_stream);
} else {
- // To escape an extended character that is not in the Basic Multilingual Plane,
- // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair.
- const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800;
- const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00;
- try out_stream.writeAll("\\u");
- try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
- try out_stream.writeAll("\\u");
- try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
+ try out_stream.writeAll(value[i .. i + ulen]);
}
i += ulen - 1;
},
}
}
- try out_stream.writeAll("\"");
+ try out_stream.writeByte('\"');
return;
}
- try out_stream.writeAll("[");
+ try out_stream.writeByte('[');
+ var child_options = options;
+ if (child_options.whitespace) |*whitespace| {
+ whitespace.indent_level += 1;
+ }
for (value) |x, i| {
if (i != 0) {
- try out_stream.writeAll(",");
+ try out_stream.writeByte(',');
+ }
+ if (child_options.whitespace) |child_whitespace| {
+ try out_stream.writeByte('\n');
+ try child_whitespace.outputIndent(out_stream);
}
- try stringify(x, options, out_stream);
+ try stringify(x, child_options, out_stream);
}
- try out_stream.writeAll("]");
+ if (value.len != 0) {
+ if (options.whitespace) |whitespace| {
+ try out_stream.writeByte('\n');
+ try whitespace.outputIndent(out_stream);
+ }
+ }
+ try out_stream.writeByte(']');
return;
},
else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
@@ -2390,7 +2576,7 @@ pub fn stringify(
unreachable;
}
-fn teststringify(expected: []const u8, value: var) !void {
+fn teststringify(expected: []const u8, value: var, options: StringifyOptions) !void {
const ValidationOutStream = struct {
const Self = @This();
pub const OutStream = std.io.OutStream(*Self, Error, write);
@@ -2442,55 +2628,105 @@ fn teststringify(expected: []const u8, value: var) !void {
};
var vos = ValidationOutStream.init(expected);
- try stringify(value, StringifyOptions{}, vos.outStream());
+ try stringify(value, options, vos.outStream());
if (vos.expected_remaining.len > 0) return error.NotEnoughData;
}
test "stringify basic types" {
- try teststringify("false", false);
- try teststringify("true", true);
- try teststringify("null", @as(?u8, null));
- try teststringify("null", @as(?*u32, null));
- try teststringify("42", 42);
- try teststringify("4.2e+01", 42.0);
- try teststringify("42", @as(u8, 42));
- try teststringify("42", @as(u128, 42));
- try teststringify("4.2e+01", @as(f32, 42));
- try teststringify("4.2e+01", @as(f64, 42));
+ try teststringify("false", false, StringifyOptions{});
+ try teststringify("true", true, StringifyOptions{});
+ try teststringify("null", @as(?u8, null), StringifyOptions{});
+ try teststringify("null", @as(?*u32, null), StringifyOptions{});
+ try teststringify("42", 42, StringifyOptions{});
+ try teststringify("4.2e+01", 42.0, StringifyOptions{});
+ try teststringify("42", @as(u8, 42), StringifyOptions{});
+ try teststringify("42", @as(u128, 42), StringifyOptions{});
+ try teststringify("4.2e+01", @as(f32, 42), StringifyOptions{});
+ try teststringify("4.2e+01", @as(f64, 42), StringifyOptions{});
}
test "stringify string" {
- try teststringify("\"hello\"", "hello");
- try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r");
- try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}");
- try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}");
- try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}");
- try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}");
- try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}");
- try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}");
- try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}");
- try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}");
- try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}");
+ try teststringify("\"hello\"", "hello", StringifyOptions{});
+ try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{});
+ try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
+ try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{});
+ try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
+ try teststringify("\"with unicode\u{80}\"", "with unicode\u{80}", StringifyOptions{});
+ try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
+ try teststringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", StringifyOptions{});
+ try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
+ try teststringify("\"with unicode\u{100}\"", "with unicode\u{100}", StringifyOptions{});
+ try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
+ try teststringify("\"with unicode\u{800}\"", "with unicode\u{800}", StringifyOptions{});
+ try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
+ try teststringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", StringifyOptions{});
+ try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
+ try teststringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", StringifyOptions{});
+ try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
+ try teststringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", StringifyOptions{});
+ try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
+ try teststringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", StringifyOptions{});
+ try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
}
test "stringify tagged unions" {
try teststringify("42", union(enum) {
Foo: u32,
Bar: bool,
- }{ .Foo = 42 });
+ }{ .Foo = 42 }, StringifyOptions{});
}
test "stringify struct" {
try teststringify("{\"foo\":42}", struct {
foo: u32,
- }{ .foo = 42 });
+ }{ .foo = 42 }, StringifyOptions{});
+}
+
+test "stringify struct with indentation" {
+ try teststringify(
+ \\{
+ \\ "foo": 42,
+ \\ "bar": [
+ \\ 1,
+ \\ 2,
+ \\ 3
+ \\ ]
+ \\}
+ ,
+ struct {
+ foo: u32,
+ bar: [3]u32,
+ }{
+ .foo = 42,
+ .bar = .{ 1, 2, 3 },
+ },
+ StringifyOptions{
+ .whitespace = .{},
+ },
+ );
+ try teststringify(
+ "{\n\t\"foo\":42,\n\t\"bar\":[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}",
+ struct {
+ foo: u32,
+ bar: [3]u32,
+ }{
+ .foo = 42,
+ .bar = .{ 1, 2, 3 },
+ },
+ StringifyOptions{
+ .whitespace = .{
+ .indent = .Tab,
+ .separator = false,
+ },
+ },
+ );
}
test "stringify struct with void field" {
try teststringify("{\"foo\":42}", struct {
foo: u32,
bar: void = {},
- }{ .foo = 42 });
+ }{ .foo = 42 }, StringifyOptions{});
}
test "stringify array of structs" {
@@ -2501,7 +2737,7 @@ test "stringify array of structs" {
MyStruct{ .foo = 42 },
MyStruct{ .foo = 100 },
MyStruct{ .foo = 1000 },
- });
+ }, StringifyOptions{});
}
test "stringify struct with custom stringifier" {
@@ -2515,7 +2751,7 @@ test "stringify struct with custom stringifier" {
) !void {
try out_stream.writeAll("[\"something special\",");
try stringify(42, options, out_stream);
- try out_stream.writeAll("]");
+ try out_stream.writeByte(']');
}
- }{ .foo = 42 });
+ }{ .foo = 42 }, StringifyOptions{});
}
diff --git a/lib/std/json/write_stream.zig b/lib/std/json/write_stream.zig
@@ -21,14 +21,10 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
pub const Stream = OutStream;
- /// The string used for indenting.
- one_indent: []const u8 = " ",
-
- /// The string used as a newline character.
- newline: []const u8 = "\n",
-
- /// The string used as spacing.
- space: []const u8 = " ",
+ whitespace: std.json.StringifyOptions.Whitespace = std.json.StringifyOptions.Whitespace{
+ .indent_level = 0,
+ .indent = .{ .Space = 1 },
+ },
stream: OutStream,
state_index: usize,
@@ -49,12 +45,14 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField
try self.stream.writeByte('[');
self.state[self.state_index] = State.ArrayStart;
+ self.whitespace.indent_level += 1;
}
pub fn beginObject(self: *Self) !void {
assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField
try self.stream.writeByte('{');
self.state[self.state_index] = State.ObjectStart;
+ self.whitespace.indent_level += 1;
}
pub fn arrayElem(self: *Self) !void {
@@ -90,8 +88,10 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
self.pushState(.Value);
try self.indent();
try self.writeEscapedString(name);
- try self.stream.writeAll(":");
- try self.stream.writeAll(self.space);
+ try self.stream.writeByte(':');
+ if (self.whitespace.separator) {
+ try self.stream.writeByte(' ');
+ }
},
}
}
@@ -103,10 +103,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
.ObjectStart => unreachable,
.Object => unreachable,
.ArrayStart => {
+ self.whitespace.indent_level -= 1;
try self.stream.writeByte(']');
self.popState();
},
.Array => {
+ self.whitespace.indent_level -= 1;
try self.indent();
self.popState();
try self.stream.writeByte(']');
@@ -121,10 +123,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
.ArrayStart => unreachable,
.Array => unreachable,
.ObjectStart => {
+ self.whitespace.indent_level -= 1;
try self.stream.writeByte('}');
self.popState();
},
.Object => {
+ self.whitespace.indent_level -= 1;
try self.indent();
self.popState();
try self.stream.writeByte('}');
@@ -134,17 +138,13 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
pub fn emitNull(self: *Self) !void {
assert(self.state[self.state_index] == State.Value);
- try self.stream.writeAll("null");
+ try self.stringify(null);
self.popState();
}
pub fn emitBool(self: *Self, value: bool) !void {
assert(self.state[self.state_index] == State.Value);
- if (value) {
- try self.stream.writeAll("true");
- } else {
- try self.stream.writeAll("false");
- }
+ try self.stringify(value);
self.popState();
}
@@ -185,57 +185,19 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
}
fn writeEscapedString(self: *Self, string: []const u8) !void {
- try self.stream.writeByte('"');
- for (string) |s| {
- switch (s) {
- '"' => try self.stream.writeAll("\\\""),
- '\t' => try self.stream.writeAll("\\t"),
- '\r' => try self.stream.writeAll("\\r"),
- '\n' => try self.stream.writeAll("\\n"),
- 8 => try self.stream.writeAll("\\b"),
- 12 => try self.stream.writeAll("\\f"),
- '\\' => try self.stream.writeAll("\\\\"),
- else => try self.stream.writeByte(s),
- }
- }
- try self.stream.writeByte('"');
+ assert(std.unicode.utf8ValidateSlice(string));
+ try self.stringify(string);
}
/// Writes the complete json into the output stream
pub fn emitJson(self: *Self, json: std.json.Value) Stream.Error!void {
- switch (json) {
- .Null => try self.emitNull(),
- .Bool => |inner| try self.emitBool(inner),
- .Integer => |inner| try self.emitNumber(inner),
- .Float => |inner| try self.emitNumber(inner),
- .String => |inner| try self.emitString(inner),
- .Array => |inner| {
- try self.beginArray();
- for (inner.span()) |elem| {
- try self.arrayElem();
- try self.emitJson(elem);
- }
- try self.endArray();
- },
- .Object => |inner| {
- try self.beginObject();
- var it = inner.iterator();
- while (it.next()) |entry| {
- try self.objectField(entry.key);
- try self.emitJson(entry.value);
- }
- try self.endObject();
- },
- }
+ try self.stringify(json);
}
fn indent(self: *Self) !void {
assert(self.state_index >= 1);
- try self.stream.writeAll(self.newline);
- var i: usize = 0;
- while (i < self.state_index - 1) : (i += 1) {
- try self.stream.writeAll(self.one_indent);
- }
+ try self.stream.writeByte('\n');
+ try self.whitespace.outputIndent(self.stream);
}
fn pushState(self: *Self, state: State) void {
@@ -246,6 +208,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
fn popState(self: *Self) void {
self.state_index -= 1;
}
+
+ fn stringify(self: *Self, value: var) !void {
+ try std.json.stringify(value, std.json.StringifyOptions{
+ .whitespace = self.whitespace,
+ }, self.stream);
+ }
};
}