authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-03-31 12:07:25-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-03-31 12:07:25-04:00
logd34a3c66b36e3afc8ea7f275b449033fa7a1b4bc
tree295a4ad2b0e6cec0c19c5807f18daced97cd3dd6
parent28b7306a31f52f53e7936018c01c0e8d24ebf6ea
parent7a3d700fd9d6dcdb559aaae9159af6725b28defb
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #4543 from daurnimator/cleanup-json

std.json improvements

2 files changed, 354 insertions(+), 150 deletions(-)

lib/std/json.zig+327-91
......@@ -1233,42 +1233,119 @@ pub const Value = union(enum) {
12331233 Array: Array,
12341234 Object: ObjectMap,
12351235
1236 pub fn jsonStringify(
1237 value: @This(),
1238 options: StringifyOptions,
1239 out_stream: var,
1240 ) @TypeOf(out_stream).Error!void {
1241 switch (value) {
1242 .Null => try stringify(null, options, out_stream),
1243 .Bool => |inner| try stringify(inner, options, out_stream),
1244 .Integer => |inner| try stringify(inner, options, out_stream),
1245 .Float => |inner| try stringify(inner, options, out_stream),
1246 .String => |inner| try stringify(inner, options, out_stream),
1247 .Array => |inner| try stringify(inner.span(), options, out_stream),
1248 .Object => |inner| {
1249 try out_stream.writeByte('{');
1250 var field_output = false;
1251 var child_options = options;
1252 if (child_options.whitespace) |*child_whitespace| {
1253 child_whitespace.indent_level += 1;
1254 }
1255 var it = inner.iterator();
1256 while (it.next()) |entry| {
1257 if (!field_output) {
1258 field_output = true;
1259 } else {
1260 try out_stream.writeByte(',');
1261 }
1262 if (child_options.whitespace) |child_whitespace| {
1263 try out_stream.writeByte('\n');
1264 try child_whitespace.outputIndent(out_stream);
1265 }
1266
1267 try stringify(entry.key, options, out_stream);
1268 try out_stream.writeByte(':');
1269 if (child_options.whitespace) |child_whitespace| {
1270 if (child_whitespace.separator) {
1271 try out_stream.writeByte(' ');
1272 }
1273 }
1274 try stringify(entry.value, child_options, out_stream);
1275 }
1276 if (field_output) {
1277 if (options.whitespace) |whitespace| {
1278 try out_stream.writeByte('\n');
1279 try whitespace.outputIndent(out_stream);
1280 }
1281 }
1282 try out_stream.writeByte('}');
1283 },
1284 }
1285 }
1286
12361287 pub fn dump(self: Value) void {
12371288 var held = std.debug.getStderrMutex().acquire();
12381289 defer held.release();
12391290
12401291 const stderr = std.debug.getStderrStream();
1241 self.dumpStream(stderr, 1024) catch return;
1292 std.json.stringify(self, std.json.StringifyOptions{ .whitespace = null }, stderr) catch return;
12421293 }
1294};
12431295
1244 pub fn dumpIndent(self: Value, comptime indent: usize) void {
1245 if (indent == 0) {
1246 self.dump();
1247 } else {
1248 var held = std.debug.getStderrMutex().acquire();
1249 defer held.release();
1250
1251 const stderr = std.debug.getStderrStream();
1252 self.dumpStreamIndent(indent, stderr, 1024) catch return;
1253 }
1296test "Value.jsonStringify" {
1297 {
1298 var buffer: [10]u8 = undefined;
1299 var fbs = std.io.fixedBufferStream(&buffer);
1300 try @as(Value, .Null).jsonStringify(.{}, fbs.outStream());
1301 testing.expectEqualSlices(u8, fbs.getWritten(), "null");
12541302 }
1255
1256 pub fn dumpStream(self: @This(), stream: var, comptime max_depth: usize) !void {
1257 var w = std.json.WriteStream(@TypeOf(stream).Child, max_depth).init(stream);
1258 w.newline = "";
1259 w.one_indent = "";
1260 w.space = "";
1261 try w.emitJson(self);
1303 {
1304 var buffer: [10]u8 = undefined;
1305 var fbs = std.io.fixedBufferStream(&buffer);
1306 try (Value{ .Bool = true }).jsonStringify(.{}, fbs.outStream());
1307 testing.expectEqualSlices(u8, fbs.getWritten(), "true");
12621308 }
1263
1264 pub fn dumpStreamIndent(self: @This(), comptime indent: usize, stream: var, comptime max_depth: usize) !void {
1265 var one_indent = " " ** indent;
1266
1267 var w = std.json.WriteStream(@TypeOf(stream).Child, max_depth).init(stream);
1268 w.one_indent = one_indent;
1269 try w.emitJson(self);
1309 {
1310 var buffer: [10]u8 = undefined;
1311 var fbs = std.io.fixedBufferStream(&buffer);
1312 try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.outStream());
1313 testing.expectEqualSlices(u8, fbs.getWritten(), "42");
12701314 }
1271};
1315 {
1316 var buffer: [10]u8 = undefined;
1317 var fbs = std.io.fixedBufferStream(&buffer);
1318 try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.outStream());
1319 testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01");
1320 }
1321 {
1322 var buffer: [10]u8 = undefined;
1323 var fbs = std.io.fixedBufferStream(&buffer);
1324 try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.outStream());
1325 testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\"");
1326 }
1327 {
1328 var buffer: [10]u8 = undefined;
1329 var fbs = std.io.fixedBufferStream(&buffer);
1330 try (Value{
1331 .Array = Array.fromOwnedSlice(undefined, &[_]Value{
1332 .{ .Integer = 1 },
1333 .{ .Integer = 2 },
1334 .{ .Integer = 3 },
1335 }),
1336 }).jsonStringify(.{}, fbs.outStream());
1337 testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]");
1338 }
1339 {
1340 var buffer: [10]u8 = undefined;
1341 var fbs = std.io.fixedBufferStream(&buffer);
1342 var obj = ObjectMap.init(testing.allocator);
1343 defer obj.deinit();
1344 try obj.putNoClobber("a", .{ .String = "b" });
1345 try (Value{ .Object = obj }).jsonStringify(.{}, fbs.outStream());
1346 testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}");
1347 }
1348}
12721349
12731350pub const ParseOptions = struct {
12741351 allocator: ?*Allocator = null,
......@@ -2241,11 +2318,86 @@ test "string copy option" {
22412318}
22422319
22432320pub const StringifyOptions = struct {
2244 // TODO: indentation options?
2245 // TODO: make escaping '/' in strings optional?
2246 // TODO: allow picking if []u8 is string or array?
2321 pub const Whitespace = struct {
2322 /// How many indentation levels deep are we?
2323 indent_level: usize = 0,
2324
2325 pub const Indentation = union(enum) {
2326 Space: u8,
2327 Tab: void,
2328 };
2329
2330 /// What character(s) should be used for indentation?
2331 indent: Indentation = Indentation{ .Space = 4 },
2332
2333 fn outputIndent(
2334 whitespace: @This(),
2335 out_stream: var,
2336 ) @TypeOf(out_stream).Error!void {
2337 var char: u8 = undefined;
2338 var n_chars: usize = undefined;
2339 switch (whitespace.indent) {
2340 .Space => |n_spaces| {
2341 char = ' ';
2342 n_chars = n_spaces;
2343 },
2344 .Tab => {
2345 char = '\t';
2346 n_chars = 1;
2347 },
2348 }
2349 n_chars *= whitespace.indent_level;
2350 try out_stream.writeByteNTimes(char, n_chars);
2351 }
2352
2353 /// After a colon, should whitespace be inserted?
2354 separator: bool = true,
2355 };
2356
2357 /// Controls the whitespace emitted
2358 whitespace: ?Whitespace = null,
2359
2360 /// Should []u8 be serialised as a string? or an array?
2361 pub const StringOptions = union(enum) {
2362 Array,
2363
2364 /// String output options
2365 const StringOutputOptions = struct {
2366 /// Should '/' be escaped in strings?
2367 escape_solidus: bool = false,
2368
2369 /// Should unicode characters be escaped in strings?
2370 escape_unicode: bool = false,
2371 };
2372 String: StringOutputOptions,
2373 };
2374
2375 string: StringOptions = StringOptions{ .String = .{} },
22472376};
22482377
2378fn outputUnicodeEscape(
2379 codepoint: u21,
2380 out_stream: var,
2381) !void {
2382 if (codepoint <= 0xFFFF) {
2383 // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),
2384 // then it may be represented as a six-character sequence: a reverse solidus, followed
2385 // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.
2386 try out_stream.writeAll("\\u");
2387 try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2388 } else {
2389 assert(codepoint <= 0x10FFFF);
2390 // To escape an extended character that is not in the Basic Multilingual Plane,
2391 // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair.
2392 const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800;
2393 const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00;
2394 try out_stream.writeAll("\\u");
2395 try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2396 try out_stream.writeAll("\\u");
2397 try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2398 }
2399}
2400
22492401pub fn stringify(
22502402 value: var,
22512403 options: StringifyOptions,
......@@ -2262,11 +2414,14 @@ pub fn stringify(
22622414 .Bool => {
22632415 return out_stream.writeAll(if (value) "true" else "false");
22642416 },
2417 .Null => {
2418 return out_stream.writeAll("null");
2419 },
22652420 .Optional => {
22662421 if (value) |payload| {
22672422 return try stringify(payload, options, out_stream);
22682423 } else {
2269 return out_stream.writeAll("null");
2424 return try stringify(null, options, out_stream);
22702425 }
22712426 },
22722427 .Enum => {
......@@ -2297,8 +2452,12 @@ pub fn stringify(
22972452 return value.jsonStringify(options, out_stream);
22982453 }
22992454
2300 try out_stream.writeAll("{");
2455 try out_stream.writeByte('{');
23012456 comptime var field_output = false;
2457 var child_options = options;
2458 if (child_options.whitespace) |*child_whitespace| {
2459 child_whitespace.indent_level += 1;
2460 }
23022461 inline for (S.fields) |Field, field_i| {
23032462 // don't include void fields
23042463 if (Field.field_type == void) continue;
......@@ -2306,14 +2465,28 @@ pub fn stringify(
23062465 if (!field_output) {
23072466 field_output = true;
23082467 } else {
2309 try out_stream.writeAll(",");
2468 try out_stream.writeByte(',');
2469 }
2470 if (child_options.whitespace) |child_whitespace| {
2471 try out_stream.writeByte('\n');
2472 try child_whitespace.outputIndent(out_stream);
23102473 }
2311
23122474 try stringify(Field.name, options, out_stream);
2313 try out_stream.writeAll(":");
2314 try stringify(@field(value, Field.name), options, out_stream);
2475 try out_stream.writeByte(':');
2476 if (child_options.whitespace) |child_whitespace| {
2477 if (child_whitespace.separator) {
2478 try out_stream.writeByte(' ');
2479 }
2480 }
2481 try stringify(@field(value, Field.name), child_options, out_stream);
2482 }
2483 if (field_output) {
2484 if (options.whitespace) |whitespace| {
2485 try out_stream.writeByte('\n');
2486 try whitespace.outputIndent(out_stream);
2487 }
23152488 }
2316 try out_stream.writeAll("}");
2489 try out_stream.writeByte('}');
23172490 return;
23182491 },
23192492 .Pointer => |ptr_info| switch (ptr_info.size) {
......@@ -2329,17 +2502,26 @@ pub fn stringify(
23292502 },
23302503 // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)
23312504 .Slice => {
2332 if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) {
2333 try out_stream.writeAll("\"");
2505 if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(value)) {
2506 try out_stream.writeByte('\"');
23342507 var i: usize = 0;
23352508 while (i < value.len) : (i += 1) {
23362509 switch (value[i]) {
2337 // normal ascii characters
2338 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try out_stream.writeAll(value[i .. i + 1]),
2339 // control characters with short escapes
2510 // normal ascii character
2511 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try out_stream.writeByte(c),
2512 // only 2 characters that *must* be escaped
23402513 '\\' => try out_stream.writeAll("\\\\"),
23412514 '\"' => try out_stream.writeAll("\\\""),
2342 '/' => try out_stream.writeAll("\\/"),
2515 // solidus is optional to escape
2516 '/' => {
2517 if (options.string.String.escape_solidus) {
2518 try out_stream.writeAll("\\/");
2519 } else {
2520 try out_stream.writeByte('\\');
2521 }
2522 },
2523 // control characters with short escapes
2524 // TODO: option to switch between unicode and 'short' forms?
23432525 0x8 => try out_stream.writeAll("\\b"),
23442526 0xC => try out_stream.writeAll("\\f"),
23452527 '\n' => try out_stream.writeAll("\\n"),
......@@ -2347,39 +2529,43 @@ pub fn stringify(
23472529 '\t' => try out_stream.writeAll("\\t"),
23482530 else => {
23492531 const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable;
2350 const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;
2351 if (codepoint <= 0xFFFF) {
2352 // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),
2353 // then it may be represented as a six-character sequence: a reverse solidus, followed
2354 // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.
2355 try out_stream.writeAll("\\u");
2356 try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2532 // control characters (only things left with 1 byte length) should always be printed as unicode escapes
2533 if (ulen == 1 or options.string.String.escape_unicode) {
2534 const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;
2535 try outputUnicodeEscape(codepoint, out_stream);
23572536 } else {
2358 // To escape an extended character that is not in the Basic Multilingual Plane,
2359 // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair.
2360 const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800;
2361 const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00;
2362 try out_stream.writeAll("\\u");
2363 try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2364 try out_stream.writeAll("\\u");
2365 try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2537 try out_stream.writeAll(value[i .. i + ulen]);
23662538 }
23672539 i += ulen - 1;
23682540 },
23692541 }
23702542 }
2371 try out_stream.writeAll("\"");
2543 try out_stream.writeByte('\"');
23722544 return;
23732545 }
23742546
2375 try out_stream.writeAll("[");
2547 try out_stream.writeByte('[');
2548 var child_options = options;
2549 if (child_options.whitespace) |*whitespace| {
2550 whitespace.indent_level += 1;
2551 }
23762552 for (value) |x, i| {
23772553 if (i != 0) {
2378 try out_stream.writeAll(",");
2554 try out_stream.writeByte(',');
2555 }
2556 if (child_options.whitespace) |child_whitespace| {
2557 try out_stream.writeByte('\n');
2558 try child_whitespace.outputIndent(out_stream);
23792559 }
2380 try stringify(x, options, out_stream);
2560 try stringify(x, child_options, out_stream);
23812561 }
2382 try out_stream.writeAll("]");
2562 if (value.len != 0) {
2563 if (options.whitespace) |whitespace| {
2564 try out_stream.writeByte('\n');
2565 try whitespace.outputIndent(out_stream);
2566 }
2567 }
2568 try out_stream.writeByte(']');
23832569 return;
23842570 },
23852571 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
......@@ -2390,7 +2576,7 @@ pub fn stringify(
23902576 unreachable;
23912577}
23922578
2393fn teststringify(expected: []const u8, value: var) !void {
2579fn teststringify(expected: []const u8, value: var, options: StringifyOptions) !void {
23942580 const ValidationOutStream = struct {
23952581 const Self = @This();
23962582 pub const OutStream = std.io.OutStream(*Self, Error, write);
......@@ -2442,55 +2628,105 @@ fn teststringify(expected: []const u8, value: var) !void {
24422628 };
24432629
24442630 var vos = ValidationOutStream.init(expected);
2445 try stringify(value, StringifyOptions{}, vos.outStream());
2631 try stringify(value, options, vos.outStream());
24462632 if (vos.expected_remaining.len > 0) return error.NotEnoughData;
24472633}
24482634
24492635test "stringify basic types" {
2450 try teststringify("false", false);
2451 try teststringify("true", true);
2452 try teststringify("null", @as(?u8, null));
2453 try teststringify("null", @as(?*u32, null));
2454 try teststringify("42", 42);
2455 try teststringify("4.2e+01", 42.0);
2456 try teststringify("42", @as(u8, 42));
2457 try teststringify("42", @as(u128, 42));
2458 try teststringify("4.2e+01", @as(f32, 42));
2459 try teststringify("4.2e+01", @as(f64, 42));
2636 try teststringify("false", false, StringifyOptions{});
2637 try teststringify("true", true, StringifyOptions{});
2638 try teststringify("null", @as(?u8, null), StringifyOptions{});
2639 try teststringify("null", @as(?*u32, null), StringifyOptions{});
2640 try teststringify("42", 42, StringifyOptions{});
2641 try teststringify("4.2e+01", 42.0, StringifyOptions{});
2642 try teststringify("42", @as(u8, 42), StringifyOptions{});
2643 try teststringify("42", @as(u128, 42), StringifyOptions{});
2644 try teststringify("4.2e+01", @as(f32, 42), StringifyOptions{});
2645 try teststringify("4.2e+01", @as(f64, 42), StringifyOptions{});
24602646}
24612647
24622648test "stringify string" {
2463 try teststringify("\"hello\"", "hello");
2464 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r");
2465 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}");
2466 try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}");
2467 try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}");
2468 try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}");
2469 try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}");
2470 try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}");
2471 try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}");
2472 try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}");
2473 try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}");
2649 try teststringify("\"hello\"", "hello", StringifyOptions{});
2650 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{});
2651 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2652 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{});
2653 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2654 try teststringify("\"with unicode\u{80}\"", "with unicode\u{80}", StringifyOptions{});
2655 try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2656 try teststringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", StringifyOptions{});
2657 try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2658 try teststringify("\"with unicode\u{100}\"", "with unicode\u{100}", StringifyOptions{});
2659 try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2660 try teststringify("\"with unicode\u{800}\"", "with unicode\u{800}", StringifyOptions{});
2661 try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2662 try teststringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", StringifyOptions{});
2663 try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2664 try teststringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", StringifyOptions{});
2665 try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2666 try teststringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", StringifyOptions{});
2667 try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2668 try teststringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", StringifyOptions{});
2669 try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
24742670}
24752671
24762672test "stringify tagged unions" {
24772673 try teststringify("42", union(enum) {
24782674 Foo: u32,
24792675 Bar: bool,
2480 }{ .Foo = 42 });
2676 }{ .Foo = 42 }, StringifyOptions{});
24812677}
24822678
24832679test "stringify struct" {
24842680 try teststringify("{\"foo\":42}", struct {
24852681 foo: u32,
2486 }{ .foo = 42 });
2682 }{ .foo = 42 }, StringifyOptions{});
2683}
2684
2685test "stringify struct with indentation" {
2686 try teststringify(
2687 \\{
2688 \\ "foo": 42,
2689 \\ "bar": [
2690 \\ 1,
2691 \\ 2,
2692 \\ 3
2693 \\ ]
2694 \\}
2695 ,
2696 struct {
2697 foo: u32,
2698 bar: [3]u32,
2699 }{
2700 .foo = 42,
2701 .bar = .{ 1, 2, 3 },
2702 },
2703 StringifyOptions{
2704 .whitespace = .{},
2705 },
2706 );
2707 try teststringify(
2708 "{\n\t\"foo\":42,\n\t\"bar\":[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}",
2709 struct {
2710 foo: u32,
2711 bar: [3]u32,
2712 }{
2713 .foo = 42,
2714 .bar = .{ 1, 2, 3 },
2715 },
2716 StringifyOptions{
2717 .whitespace = .{
2718 .indent = .Tab,
2719 .separator = false,
2720 },
2721 },
2722 );
24872723}
24882724
24892725test "stringify struct with void field" {
24902726 try teststringify("{\"foo\":42}", struct {
24912727 foo: u32,
24922728 bar: void = {},
2493 }{ .foo = 42 });
2729 }{ .foo = 42 }, StringifyOptions{});
24942730}
24952731
24962732test "stringify array of structs" {
......@@ -2501,7 +2737,7 @@ test "stringify array of structs" {
25012737 MyStruct{ .foo = 42 },
25022738 MyStruct{ .foo = 100 },
25032739 MyStruct{ .foo = 1000 },
2504 });
2740 }, StringifyOptions{});
25052741}
25062742
25072743test "stringify struct with custom stringifier" {
......@@ -2515,7 +2751,7 @@ test "stringify struct with custom stringifier" {
25152751 ) !void {
25162752 try out_stream.writeAll("[\"something special\",");
25172753 try stringify(42, options, out_stream);
2518 try out_stream.writeAll("]");
2754 try out_stream.writeByte(']');
25192755 }
2520 }{ .foo = 42 });
2756 }{ .foo = 42 }, StringifyOptions{});
25212757}
lib/std/json/write_stream.zig+27-59
......@@ -21,14 +21,10 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
2121
2222 pub const Stream = OutStream;
2323
24 /// The string used for indenting.
25 one_indent: []const u8 = " ",
26
27 /// The string used as a newline character.
28 newline: []const u8 = "\n",
29
30 /// The string used as spacing.
31 space: []const u8 = " ",
24 whitespace: std.json.StringifyOptions.Whitespace = std.json.StringifyOptions.Whitespace{
25 .indent_level = 0,
26 .indent = .{ .Space = 1 },
27 },
3228
3329 stream: OutStream,
3430 state_index: usize,
......@@ -49,12 +45,14 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
4945 assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField
5046 try self.stream.writeByte('[');
5147 self.state[self.state_index] = State.ArrayStart;
48 self.whitespace.indent_level += 1;
5249 }
5350
5451 pub fn beginObject(self: *Self) !void {
5552 assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField
5653 try self.stream.writeByte('{');
5754 self.state[self.state_index] = State.ObjectStart;
55 self.whitespace.indent_level += 1;
5856 }
5957
6058 pub fn arrayElem(self: *Self) !void {
......@@ -90,8 +88,10 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
9088 self.pushState(.Value);
9189 try self.indent();
9290 try self.writeEscapedString(name);
93 try self.stream.writeAll(":");
94 try self.stream.writeAll(self.space);
91 try self.stream.writeByte(':');
92 if (self.whitespace.separator) {
93 try self.stream.writeByte(' ');
94 }
9595 },
9696 }
9797 }
......@@ -103,10 +103,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
103103 .ObjectStart => unreachable,
104104 .Object => unreachable,
105105 .ArrayStart => {
106 self.whitespace.indent_level -= 1;
106107 try self.stream.writeByte(']');
107108 self.popState();
108109 },
109110 .Array => {
111 self.whitespace.indent_level -= 1;
110112 try self.indent();
111113 self.popState();
112114 try self.stream.writeByte(']');
......@@ -121,10 +123,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
121123 .ArrayStart => unreachable,
122124 .Array => unreachable,
123125 .ObjectStart => {
126 self.whitespace.indent_level -= 1;
124127 try self.stream.writeByte('}');
125128 self.popState();
126129 },
127130 .Object => {
131 self.whitespace.indent_level -= 1;
128132 try self.indent();
129133 self.popState();
130134 try self.stream.writeByte('}');
......@@ -134,17 +138,13 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
134138
135139 pub fn emitNull(self: *Self) !void {
136140 assert(self.state[self.state_index] == State.Value);
137 try self.stream.writeAll("null");
141 try self.stringify(null);
138142 self.popState();
139143 }
140144
141145 pub fn emitBool(self: *Self, value: bool) !void {
142146 assert(self.state[self.state_index] == State.Value);
143 if (value) {
144 try self.stream.writeAll("true");
145 } else {
146 try self.stream.writeAll("false");
147 }
147 try self.stringify(value);
148148 self.popState();
149149 }
150150
......@@ -185,57 +185,19 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
185185 }
186186
187187 fn writeEscapedString(self: *Self, string: []const u8) !void {
188 try self.stream.writeByte('"');
189 for (string) |s| {
190 switch (s) {
191 '"' => try self.stream.writeAll("\\\""),
192 '\t' => try self.stream.writeAll("\\t"),
193 '\r' => try self.stream.writeAll("\\r"),
194 '\n' => try self.stream.writeAll("\\n"),
195 8 => try self.stream.writeAll("\\b"),
196 12 => try self.stream.writeAll("\\f"),
197 '\\' => try self.stream.writeAll("\\\\"),
198 else => try self.stream.writeByte(s),
199 }
200 }
201 try self.stream.writeByte('"');
188 assert(std.unicode.utf8ValidateSlice(string));
189 try self.stringify(string);
202190 }
203191
204192 /// Writes the complete json into the output stream
205193 pub fn emitJson(self: *Self, json: std.json.Value) Stream.Error!void {
206 switch (json) {
207 .Null => try self.emitNull(),
208 .Bool => |inner| try self.emitBool(inner),
209 .Integer => |inner| try self.emitNumber(inner),
210 .Float => |inner| try self.emitNumber(inner),
211 .String => |inner| try self.emitString(inner),
212 .Array => |inner| {
213 try self.beginArray();
214 for (inner.span()) |elem| {
215 try self.arrayElem();
216 try self.emitJson(elem);
217 }
218 try self.endArray();
219 },
220 .Object => |inner| {
221 try self.beginObject();
222 var it = inner.iterator();
223 while (it.next()) |entry| {
224 try self.objectField(entry.key);
225 try self.emitJson(entry.value);
226 }
227 try self.endObject();
228 },
229 }
194 try self.stringify(json);
230195 }
231196
232197 fn indent(self: *Self) !void {
233198 assert(self.state_index >= 1);
234 try self.stream.writeAll(self.newline);
235 var i: usize = 0;
236 while (i < self.state_index - 1) : (i += 1) {
237 try self.stream.writeAll(self.one_indent);
238 }
199 try self.stream.writeByte('\n');
200 try self.whitespace.outputIndent(self.stream);
239201 }
240202
241203 fn pushState(self: *Self, state: State) void {
......@@ -246,6 +208,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
246208 fn popState(self: *Self) void {
247209 self.state_index -= 1;
248210 }
211
212 fn stringify(self: *Self, value: var) !void {
213 try std.json.stringify(value, std.json.StringifyOptions{
214 .whitespace = self.whitespace,
215 }, self.stream);
216 }
249217 };
250218}
251219