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) {...@@ -1233,42 +1233,119 @@ pub const Value = union(enum) {
1233 Array: Array,1233 Array: Array,
1234 Object: ObjectMap,1234 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
1236 pub fn dump(self: Value) void {1287 pub fn dump(self: Value) void {
1237 var held = std.debug.getStderrMutex().acquire();1288 var held = std.debug.getStderrMutex().acquire();
1238 defer held.release();1289 defer held.release();
12391290
1240 const stderr = std.debug.getStderrStream();1291 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;
1242 }1293 }
1294};
12431295
1244 pub fn dumpIndent(self: Value, comptime indent: usize) void {1296test "Value.jsonStringify" {
1245 if (indent == 0) {1297 {
1246 self.dump();1298 var buffer: [10]u8 = undefined;
1247 } else {1299 var fbs = std.io.fixedBufferStream(&buffer);
1248 var held = std.debug.getStderrMutex().acquire();1300 try @as(Value, .Null).jsonStringify(.{}, fbs.outStream());
1249 defer held.release();1301 testing.expectEqualSlices(u8, fbs.getWritten(), "null");
1250
1251 const stderr = std.debug.getStderrStream();
1252 self.dumpStreamIndent(indent, stderr, 1024) catch return;
1253 }
1254 }1302 }
12551303 {
1256 pub fn dumpStream(self: @This(), stream: var, comptime max_depth: usize) !void {1304 var buffer: [10]u8 = undefined;
1257 var w = std.json.WriteStream(@TypeOf(stream).Child, max_depth).init(stream);1305 var fbs = std.io.fixedBufferStream(&buffer);
1258 w.newline = "";1306 try (Value{ .Bool = true }).jsonStringify(.{}, fbs.outStream());
1259 w.one_indent = "";1307 testing.expectEqualSlices(u8, fbs.getWritten(), "true");
1260 w.space = "";
1261 try w.emitJson(self);
1262 }1308 }
12631309 {
1264 pub fn dumpStreamIndent(self: @This(), comptime indent: usize, stream: var, comptime max_depth: usize) !void {1310 var buffer: [10]u8 = undefined;
1265 var one_indent = " " ** indent;1311 var fbs = std.io.fixedBufferStream(&buffer);
12661312 try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.outStream());
1267 var w = std.json.WriteStream(@TypeOf(stream).Child, max_depth).init(stream);1313 testing.expectEqualSlices(u8, fbs.getWritten(), "42");
1268 w.one_indent = one_indent;
1269 try w.emitJson(self);
1270 }1314 }
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
1273pub const ParseOptions = struct {1350pub const ParseOptions = struct {
1274 allocator: ?*Allocator = null,1351 allocator: ?*Allocator = null,
...@@ -2241,11 +2318,86 @@ test "string copy option" {...@@ -2241,11 +2318,86 @@ test "string copy option" {
2241}2318}
22422319
2243pub const StringifyOptions = struct {2320pub const StringifyOptions = struct {
2244 // TODO: indentation options?2321 pub const Whitespace = struct {
2245 // TODO: make escaping '/' in strings optional?2322 /// How many indentation levels deep are we?
2246 // TODO: allow picking if []u8 is string or array?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 = .{} },
2247};2376};
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
2249pub fn stringify(2401pub fn stringify(
2250 value: var,2402 value: var,
2251 options: StringifyOptions,2403 options: StringifyOptions,
...@@ -2262,11 +2414,14 @@ pub fn stringify(...@@ -2262,11 +2414,14 @@ pub fn stringify(
2262 .Bool => {2414 .Bool => {
2263 return out_stream.writeAll(if (value) "true" else "false");2415 return out_stream.writeAll(if (value) "true" else "false");
2264 },2416 },
2417 .Null => {
2418 return out_stream.writeAll("null");
2419 },
2265 .Optional => {2420 .Optional => {
2266 if (value) |payload| {2421 if (value) |payload| {
2267 return try stringify(payload, options, out_stream);2422 return try stringify(payload, options, out_stream);
2268 } else {2423 } else {
2269 return out_stream.writeAll("null");2424 return try stringify(null, options, out_stream);
2270 }2425 }
2271 },2426 },
2272 .Enum => {2427 .Enum => {
...@@ -2297,8 +2452,12 @@ pub fn stringify(...@@ -2297,8 +2452,12 @@ pub fn stringify(
2297 return value.jsonStringify(options, out_stream);2452 return value.jsonStringify(options, out_stream);
2298 }2453 }
22992454
2300 try out_stream.writeAll("{");2455 try out_stream.writeByte('{');
2301 comptime var field_output = false;2456 comptime var field_output = false;
2457 var child_options = options;
2458 if (child_options.whitespace) |*child_whitespace| {
2459 child_whitespace.indent_level += 1;
2460 }
2302 inline for (S.fields) |Field, field_i| {2461 inline for (S.fields) |Field, field_i| {
2303 // don't include void fields2462 // don't include void fields
2304 if (Field.field_type == void) continue;2463 if (Field.field_type == void) continue;
...@@ -2306,14 +2465,28 @@ pub fn stringify(...@@ -2306,14 +2465,28 @@ pub fn stringify(
2306 if (!field_output) {2465 if (!field_output) {
2307 field_output = true;2466 field_output = true;
2308 } else {2467 } 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);
2310 }2473 }
2311
2312 try stringify(Field.name, options, out_stream);2474 try stringify(Field.name, options, out_stream);
2313 try out_stream.writeAll(":");2475 try out_stream.writeByte(':');
2314 try stringify(@field(value, Field.name), options, out_stream);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 }
2315 }2488 }
2316 try out_stream.writeAll("}");2489 try out_stream.writeByte('}');
2317 return;2490 return;
2318 },2491 },
2319 .Pointer => |ptr_info| switch (ptr_info.size) {2492 .Pointer => |ptr_info| switch (ptr_info.size) {
...@@ -2329,17 +2502,26 @@ pub fn stringify(...@@ -2329,17 +2502,26 @@ pub fn stringify(
2329 },2502 },
2330 // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)2503 // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)
2331 .Slice => {2504 .Slice => {
2332 if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) {2505 if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(value)) {
2333 try out_stream.writeAll("\"");2506 try out_stream.writeByte('\"');
2334 var i: usize = 0;2507 var i: usize = 0;
2335 while (i < value.len) : (i += 1) {2508 while (i < value.len) : (i += 1) {
2336 switch (value[i]) {2509 switch (value[i]) {
2337 // normal ascii characters2510 // normal ascii character
2338 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try out_stream.writeAll(value[i .. i + 1]),2511 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try out_stream.writeByte(c),
2339 // control characters with short escapes2512 // only 2 characters that *must* be escaped
2340 '\\' => try out_stream.writeAll("\\\\"),2513 '\\' => try out_stream.writeAll("\\\\"),
2341 '\"' => try out_stream.writeAll("\\\""),2514 '\"' => 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?
2343 0x8 => try out_stream.writeAll("\\b"),2525 0x8 => try out_stream.writeAll("\\b"),
2344 0xC => try out_stream.writeAll("\\f"),2526 0xC => try out_stream.writeAll("\\f"),
2345 '\n' => try out_stream.writeAll("\\n"),2527 '\n' => try out_stream.writeAll("\\n"),
...@@ -2347,39 +2529,43 @@ pub fn stringify(...@@ -2347,39 +2529,43 @@ pub fn stringify(
2347 '\t' => try out_stream.writeAll("\\t"),2529 '\t' => try out_stream.writeAll("\\t"),
2348 else => {2530 else => {
2349 const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable;2531 const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable;
2350 const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;2532 // control characters (only things left with 1 byte length) should always be printed as unicode escapes
2351 if (codepoint <= 0xFFFF) {2533 if (ulen == 1 or options.string.String.escape_unicode) {
2352 // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),2534 const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;
2353 // then it may be represented as a six-character sequence: a reverse solidus, followed2535 try outputUnicodeEscape(codepoint, out_stream);
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);
2357 } else {2536 } else {
2358 // To escape an extended character that is not in the Basic Multilingual Plane,2537 try out_stream.writeAll(value[i .. i + ulen]);
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);
2366 }2538 }
2367 i += ulen - 1;2539 i += ulen - 1;
2368 },2540 },
2369 }2541 }
2370 }2542 }
2371 try out_stream.writeAll("\"");2543 try out_stream.writeByte('\"');
2372 return;2544 return;
2373 }2545 }
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 }
2376 for (value) |x, i| {2552 for (value) |x, i| {
2377 if (i != 0) {2553 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);
2379 }2559 }
2380 try stringify(x, options, out_stream);2560 try stringify(x, child_options, out_stream);
2381 }2561 }
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(']');
2383 return;2569 return;
2384 },2570 },
2385 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),2571 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
...@@ -2390,7 +2576,7 @@ pub fn stringify(...@@ -2390,7 +2576,7 @@ pub fn stringify(
2390 unreachable;2576 unreachable;
2391}2577}
23922578
2393fn teststringify(expected: []const u8, value: var) !void {2579fn teststringify(expected: []const u8, value: var, options: StringifyOptions) !void {
2394 const ValidationOutStream = struct {2580 const ValidationOutStream = struct {
2395 const Self = @This();2581 const Self = @This();
2396 pub const OutStream = std.io.OutStream(*Self, Error, write);2582 pub const OutStream = std.io.OutStream(*Self, Error, write);
...@@ -2442,55 +2628,105 @@ fn teststringify(expected: []const u8, value: var) !void {...@@ -2442,55 +2628,105 @@ fn teststringify(expected: []const u8, value: var) !void {
2442 };2628 };
24432629
2444 var vos = ValidationOutStream.init(expected);2630 var vos = ValidationOutStream.init(expected);
2445 try stringify(value, StringifyOptions{}, vos.outStream());2631 try stringify(value, options, vos.outStream());
2446 if (vos.expected_remaining.len > 0) return error.NotEnoughData;2632 if (vos.expected_remaining.len > 0) return error.NotEnoughData;
2447}2633}
24482634
2449test "stringify basic types" {2635test "stringify basic types" {
2450 try teststringify("false", false);2636 try teststringify("false", false, StringifyOptions{});
2451 try teststringify("true", true);2637 try teststringify("true", true, StringifyOptions{});
2452 try teststringify("null", @as(?u8, null));2638 try teststringify("null", @as(?u8, null), StringifyOptions{});
2453 try teststringify("null", @as(?*u32, null));2639 try teststringify("null", @as(?*u32, null), StringifyOptions{});
2454 try teststringify("42", 42);2640 try teststringify("42", 42, StringifyOptions{});
2455 try teststringify("4.2e+01", 42.0);2641 try teststringify("4.2e+01", 42.0, StringifyOptions{});
2456 try teststringify("42", @as(u8, 42));2642 try teststringify("42", @as(u8, 42), StringifyOptions{});
2457 try teststringify("42", @as(u128, 42));2643 try teststringify("42", @as(u128, 42), StringifyOptions{});
2458 try teststringify("4.2e+01", @as(f32, 42));2644 try teststringify("4.2e+01", @as(f32, 42), StringifyOptions{});
2459 try teststringify("4.2e+01", @as(f64, 42));2645 try teststringify("4.2e+01", @as(f64, 42), StringifyOptions{});
2460}2646}
24612647
2462test "stringify string" {2648test "stringify string" {
2463 try teststringify("\"hello\"", "hello");2649 try teststringify("\"hello\"", "hello", StringifyOptions{});
2464 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r");2650 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{});
2465 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}");2651 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2466 try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}");2652 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{});
2467 try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}");2653 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2468 try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}");2654 try teststringify("\"with unicode\u{80}\"", "with unicode\u{80}", StringifyOptions{});
2469 try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}");2655 try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2470 try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}");2656 try teststringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", StringifyOptions{});
2471 try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}");2657 try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
2472 try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}");2658 try teststringify("\"with unicode\u{100}\"", "with unicode\u{100}", StringifyOptions{});
2473 try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}");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 } } });
2474}2670}
24752671
2476test "stringify tagged unions" {2672test "stringify tagged unions" {
2477 try teststringify("42", union(enum) {2673 try teststringify("42", union(enum) {
2478 Foo: u32,2674 Foo: u32,
2479 Bar: bool,2675 Bar: bool,
2480 }{ .Foo = 42 });2676 }{ .Foo = 42 }, StringifyOptions{});
2481}2677}
24822678
2483test "stringify struct" {2679test "stringify struct" {
2484 try teststringify("{\"foo\":42}", struct {2680 try teststringify("{\"foo\":42}", struct {
2485 foo: u32,2681 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 );
2487}2723}
24882724
2489test "stringify struct with void field" {2725test "stringify struct with void field" {
2490 try teststringify("{\"foo\":42}", struct {2726 try teststringify("{\"foo\":42}", struct {
2491 foo: u32,2727 foo: u32,
2492 bar: void = {},2728 bar: void = {},
2493 }{ .foo = 42 });2729 }{ .foo = 42 }, StringifyOptions{});
2494}2730}
24952731
2496test "stringify array of structs" {2732test "stringify array of structs" {
...@@ -2501,7 +2737,7 @@ test "stringify array of structs" {...@@ -2501,7 +2737,7 @@ test "stringify array of structs" {
2501 MyStruct{ .foo = 42 },2737 MyStruct{ .foo = 42 },
2502 MyStruct{ .foo = 100 },2738 MyStruct{ .foo = 100 },
2503 MyStruct{ .foo = 1000 },2739 MyStruct{ .foo = 1000 },
2504 });2740 }, StringifyOptions{});
2505}2741}
25062742
2507test "stringify struct with custom stringifier" {2743test "stringify struct with custom stringifier" {
...@@ -2515,7 +2751,7 @@ test "stringify struct with custom stringifier" {...@@ -2515,7 +2751,7 @@ test "stringify struct with custom stringifier" {
2515 ) !void {2751 ) !void {
2516 try out_stream.writeAll("[\"something special\",");2752 try out_stream.writeAll("[\"something special\",");
2517 try stringify(42, options, out_stream);2753 try stringify(42, options, out_stream);
2518 try out_stream.writeAll("]");2754 try out_stream.writeByte(']');
2519 }2755 }
2520 }{ .foo = 42 });2756 }{ .foo = 42 }, StringifyOptions{});
2521}2757}
lib/std/json/write_stream.zig+27-59
...@@ -21,14 +21,10 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {...@@ -21,14 +21,10 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
2121
22 pub const Stream = OutStream;22 pub const Stream = OutStream;
2323
24 /// The string used for indenting.24 whitespace: std.json.StringifyOptions.Whitespace = std.json.StringifyOptions.Whitespace{
25 one_indent: []const u8 = " ",25 .indent_level = 0,
2626 .indent = .{ .Space = 1 },
27 /// The string used as a newline character.27 },
28 newline: []const u8 = "\n",
29
30 /// The string used as spacing.
31 space: []const u8 = " ",
3228
33 stream: OutStream,29 stream: OutStream,
34 state_index: usize,30 state_index: usize,
...@@ -49,12 +45,14 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {...@@ -49,12 +45,14 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
49 assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField45 assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField
50 try self.stream.writeByte('[');46 try self.stream.writeByte('[');
51 self.state[self.state_index] = State.ArrayStart;47 self.state[self.state_index] = State.ArrayStart;
48 self.whitespace.indent_level += 1;
52 }49 }
5350
54 pub fn beginObject(self: *Self) !void {51 pub fn beginObject(self: *Self) !void {
55 assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField52 assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField
56 try self.stream.writeByte('{');53 try self.stream.writeByte('{');
57 self.state[self.state_index] = State.ObjectStart;54 self.state[self.state_index] = State.ObjectStart;
55 self.whitespace.indent_level += 1;
58 }56 }
5957
60 pub fn arrayElem(self: *Self) !void {58 pub fn arrayElem(self: *Self) !void {
...@@ -90,8 +88,10 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {...@@ -90,8 +88,10 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
90 self.pushState(.Value);88 self.pushState(.Value);
91 try self.indent();89 try self.indent();
92 try self.writeEscapedString(name);90 try self.writeEscapedString(name);
93 try self.stream.writeAll(":");91 try self.stream.writeByte(':');
94 try self.stream.writeAll(self.space);92 if (self.whitespace.separator) {
93 try self.stream.writeByte(' ');
94 }
95 },95 },
96 }96 }
97 }97 }
...@@ -103,10 +103,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {...@@ -103,10 +103,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
103 .ObjectStart => unreachable,103 .ObjectStart => unreachable,
104 .Object => unreachable,104 .Object => unreachable,
105 .ArrayStart => {105 .ArrayStart => {
106 self.whitespace.indent_level -= 1;
106 try self.stream.writeByte(']');107 try self.stream.writeByte(']');
107 self.popState();108 self.popState();
108 },109 },
109 .Array => {110 .Array => {
111 self.whitespace.indent_level -= 1;
110 try self.indent();112 try self.indent();
111 self.popState();113 self.popState();
112 try self.stream.writeByte(']');114 try self.stream.writeByte(']');
...@@ -121,10 +123,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {...@@ -121,10 +123,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
121 .ArrayStart => unreachable,123 .ArrayStart => unreachable,
122 .Array => unreachable,124 .Array => unreachable,
123 .ObjectStart => {125 .ObjectStart => {
126 self.whitespace.indent_level -= 1;
124 try self.stream.writeByte('}');127 try self.stream.writeByte('}');
125 self.popState();128 self.popState();
126 },129 },
127 .Object => {130 .Object => {
131 self.whitespace.indent_level -= 1;
128 try self.indent();132 try self.indent();
129 self.popState();133 self.popState();
130 try self.stream.writeByte('}');134 try self.stream.writeByte('}');
...@@ -134,17 +138,13 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {...@@ -134,17 +138,13 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
134138
135 pub fn emitNull(self: *Self) !void {139 pub fn emitNull(self: *Self) !void {
136 assert(self.state[self.state_index] == State.Value);140 assert(self.state[self.state_index] == State.Value);
137 try self.stream.writeAll("null");141 try self.stringify(null);
138 self.popState();142 self.popState();
139 }143 }
140144
141 pub fn emitBool(self: *Self, value: bool) !void {145 pub fn emitBool(self: *Self, value: bool) !void {
142 assert(self.state[self.state_index] == State.Value);146 assert(self.state[self.state_index] == State.Value);
143 if (value) {147 try self.stringify(value);
144 try self.stream.writeAll("true");
145 } else {
146 try self.stream.writeAll("false");
147 }
148 self.popState();148 self.popState();
149 }149 }
150150
...@@ -185,57 +185,19 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {...@@ -185,57 +185,19 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
185 }185 }
186186
187 fn writeEscapedString(self: *Self, string: []const u8) !void {187 fn writeEscapedString(self: *Self, string: []const u8) !void {
188 try self.stream.writeByte('"');188 assert(std.unicode.utf8ValidateSlice(string));
189 for (string) |s| {189 try self.stringify(string);
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('"');
202 }190 }
203191
204 /// Writes the complete json into the output stream192 /// Writes the complete json into the output stream
205 pub fn emitJson(self: *Self, json: std.json.Value) Stream.Error!void {193 pub fn emitJson(self: *Self, json: std.json.Value) Stream.Error!void {
206 switch (json) {194 try self.stringify(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 }
230 }195 }
231196
232 fn indent(self: *Self) !void {197 fn indent(self: *Self) !void {
233 assert(self.state_index >= 1);198 assert(self.state_index >= 1);
234 try self.stream.writeAll(self.newline);199 try self.stream.writeByte('\n');
235 var i: usize = 0;200 try self.whitespace.outputIndent(self.stream);
236 while (i < self.state_index - 1) : (i += 1) {
237 try self.stream.writeAll(self.one_indent);
238 }
239 }201 }
240202
241 fn pushState(self: *Self, state: State) void {203 fn pushState(self: *Self, state: State) void {
...@@ -246,6 +208,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {...@@ -246,6 +208,12 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
246 fn popState(self: *Self) void {208 fn popState(self: *Self) void {
247 self.state_index -= 1;209 self.state_index -= 1;
248 }210 }
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 }
249 };217 };
250}218}
251219