commit
d34a3c66b3
424
lib/std/json.zig
424
lib/std/json.zig
|
@ -1233,43 +1233,120 @@ 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
std.json.stringify(self, std.json.StringifyOptions{ .whitespace = null }, stderr) 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");
|
||||
}
|
||||
{
|
||||
var buffer: [10]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
try (Value{ .Bool = true }).jsonStringify(.{}, fbs.outStream());
|
||||
testing.expectEqualSlices(u8, fbs.getWritten(), "true");
|
||||
}
|
||||
{
|
||||
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);
|
||||
}
|
||||
try out_stream.writeAll("}");
|
||||
if (field_output) {
|
||||
if (options.whitespace) |whitespace| {
|
||||
try out_stream.writeByte('\n');
|
||||
try whitespace.outputIndent(out_stream);
|
||||
}
|
||||
}
|
||||
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(',');
|
||||
}
|
||||
try stringify(x, options, out_stream);
|
||||
if (child_options.whitespace) |child_whitespace| {
|
||||
try out_stream.writeByte('\n');
|
||||
try child_whitespace.outputIndent(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{});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue