authorgravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2023-07-21 19:56:46-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-07-21 19:56:46-04:00
log8924f81d8cd96f5a69a54d87119a748247079a09
tree4dcb427921a41d3c75d3f3f4b79937d406c0a8a6
parenta2d81c547ccdb1130c4f55bb736d82f97902a3dd
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.json: Unify stringify and writeStream (#16405)


16 files changed, 1096 insertions(+), 971 deletions(-)

CMakeLists.txt+2-1
......@@ -205,6 +205,7 @@ set(ZIG_STAGE2_SOURCES
205205 "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig"
206206 "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig"
207207 "${CMAKE_SOURCE_DIR}/lib/std/base64.zig"
208 "${CMAKE_SOURCE_DIR}/lib/std/BitStack.zig"
208209 "${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig"
209210 "${CMAKE_SOURCE_DIR}/lib/std/Build.zig"
210211 "${CMAKE_SOURCE_DIR}/lib/std/Build/Cache.zig"
......@@ -260,7 +261,7 @@ set(ZIG_STAGE2_SOURCES
260261 "${CMAKE_SOURCE_DIR}/lib/std/io/seekable_stream.zig"
261262 "${CMAKE_SOURCE_DIR}/lib/std/io/writer.zig"
262263 "${CMAKE_SOURCE_DIR}/lib/std/json.zig"
263 "${CMAKE_SOURCE_DIR}/lib/std/json/write_stream.zig"
264 "${CMAKE_SOURCE_DIR}/lib/std/json/stringify.zig"
264265 "${CMAKE_SOURCE_DIR}/lib/std/leb128.zig"
265266 "${CMAKE_SOURCE_DIR}/lib/std/linked_list.zig"
266267 "${CMAKE_SOURCE_DIR}/lib/std/log.zig"
lib/std/BitStack.zig created+86
......@@ -0,0 +1,86 @@
1//! Effectively a stack of u1 values implemented using ArrayList(u8).
2
3const BitStack = @This();
4
5const std = @import("std");
6const Allocator = std.mem.Allocator;
7const ArrayList = std.ArrayList;
8
9bytes: std.ArrayList(u8),
10bit_len: usize = 0,
11
12pub fn init(allocator: Allocator) @This() {
13 return .{
14 .bytes = std.ArrayList(u8).init(allocator),
15 };
16}
17
18pub fn deinit(self: *@This()) void {
19 self.bytes.deinit();
20 self.* = undefined;
21}
22
23pub fn ensureTotalCapacity(self: *@This(), bit_capcity: usize) Allocator.Error!void {
24 const byte_capacity = (bit_capcity + 7) >> 3;
25 try self.bytes.ensureTotalCapacity(byte_capacity);
26}
27
28pub fn push(self: *@This(), b: u1) Allocator.Error!void {
29 const byte_index = self.bit_len >> 3;
30 if (self.bytes.items.len <= byte_index) {
31 try self.bytes.append(0);
32 }
33
34 pushWithStateAssumeCapacity(self.bytes.items, &self.bit_len, b);
35}
36
37pub fn peek(self: *const @This()) u1 {
38 return peekWithState(self.bytes.items, self.bit_len);
39}
40
41pub fn pop(self: *@This()) u1 {
42 return popWithState(self.bytes.items, &self.bit_len);
43}
44
45/// Standalone function for working with a fixed-size buffer.
46pub fn pushWithStateAssumeCapacity(buf: []u8, bit_len: *usize, b: u1) void {
47 const byte_index = bit_len.* >> 3;
48 const bit_index = @as(u3, @intCast(bit_len.* & 7));
49
50 buf[byte_index] &= ~(@as(u8, 1) << bit_index);
51 buf[byte_index] |= @as(u8, b) << bit_index;
52
53 bit_len.* += 1;
54}
55
56/// Standalone function for working with a fixed-size buffer.
57pub fn peekWithState(buf: []const u8, bit_len: usize) u1 {
58 const byte_index = (bit_len - 1) >> 3;
59 const bit_index = @as(u3, @intCast((bit_len - 1) & 7));
60 return @as(u1, @intCast((buf[byte_index] >> bit_index) & 1));
61}
62
63/// Standalone function for working with a fixed-size buffer.
64pub fn popWithState(buf: []const u8, bit_len: *usize) u1 {
65 const b = peekWithState(buf, bit_len.*);
66 bit_len.* -= 1;
67 return b;
68}
69
70const testing = std.testing;
71test BitStack {
72 var stack = BitStack.init(testing.allocator);
73 defer stack.deinit();
74
75 try stack.push(1);
76 try stack.push(0);
77 try stack.push(0);
78 try stack.push(1);
79
80 try testing.expectEqual(@as(u1, 1), stack.peek());
81 try testing.expectEqual(@as(u1, 1), stack.pop());
82 try testing.expectEqual(@as(u1, 0), stack.peek());
83 try testing.expectEqual(@as(u1, 0), stack.pop());
84 try testing.expectEqual(@as(u1, 0), stack.pop());
85 try testing.expectEqual(@as(u1, 1), stack.pop());
86}
lib/std/json.zig+13-10
......@@ -43,14 +43,15 @@ test Value {
4343test writeStream {
4444 var out = ArrayList(u8).init(testing.allocator);
4545 defer out.deinit();
46 var write_stream = writeStream(out.writer(), 99);
46 var write_stream = writeStream(out.writer(), .{ .whitespace = .indent_2 });
47 defer write_stream.deinit();
4748 try write_stream.beginObject();
4849 try write_stream.objectField("foo");
49 try write_stream.emitNumber(123);
50 try write_stream.write(123);
5051 try write_stream.endObject();
5152 const expected =
5253 \\{
53 \\ "foo": 123
54 \\ "foo": 123
5455 \\}
5556 ;
5657 try testing.expectEqualSlices(u8, expected, out.items);
......@@ -98,13 +99,16 @@ pub const ParseError = @import("json/static.zig").ParseError;
9899pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError;
99100
100101pub const StringifyOptions = @import("json/stringify.zig").StringifyOptions;
101pub const encodeJsonString = @import("json/stringify.zig").encodeJsonString;
102pub const encodeJsonStringChars = @import("json/stringify.zig").encodeJsonStringChars;
103102pub const stringify = @import("json/stringify.zig").stringify;
103pub const stringifyMaxDepth = @import("json/stringify.zig").stringifyMaxDepth;
104pub const stringifyArbitraryDepth = @import("json/stringify.zig").stringifyArbitraryDepth;
104105pub const stringifyAlloc = @import("json/stringify.zig").stringifyAlloc;
105
106pub const WriteStream = @import("json/write_stream.zig").WriteStream;
107pub const writeStream = @import("json/write_stream.zig").writeStream;
106pub const writeStream = @import("json/stringify.zig").writeStream;
107pub const writeStreamMaxDepth = @import("json/stringify.zig").writeStreamMaxDepth;
108pub const writeStreamArbitraryDepth = @import("json/stringify.zig").writeStreamArbitraryDepth;
109pub const WriteStream = @import("json/stringify.zig").WriteStream;
110pub const encodeJsonString = @import("json/stringify.zig").encodeJsonString;
111pub const encodeJsonStringChars = @import("json/stringify.zig").encodeJsonStringChars;
108112
109113// Deprecations
110114pub const parse = @compileError("Deprecated; use parseFromSlice() or parseFromTokenSource() instead.");
......@@ -117,9 +121,8 @@ pub const TokenStream = @compileError("Deprecated; use json.Scanner or json.Read
117121test {
118122 _ = @import("json/test.zig");
119123 _ = @import("json/scanner.zig");
120 _ = @import("json/write_stream.zig");
121124 _ = @import("json/dynamic.zig");
122 _ = @import("json/hashmap_test.zig");
125 _ = @import("json/hashmap.zig");
123126 _ = @import("json/static.zig");
124127 _ = @import("json/stringify.zig");
125128 _ = @import("json/JSONTestSuite_test.zig");
lib/std/json/dynamic.zig+12-33
......@@ -59,44 +59,23 @@ pub const Value = union(enum) {
5959 stringify(self, .{}, stderr) catch return;
6060 }
6161
62 pub fn jsonStringify(
63 value: @This(),
64 options: StringifyOptions,
65 out_stream: anytype,
66 ) @TypeOf(out_stream).Error!void {
62 pub fn jsonStringify(value: @This(), jws: anytype) !void {
6763 switch (value) {
68 .null => try stringify(null, options, out_stream),
69 .bool => |inner| try stringify(inner, options, out_stream),
70 .integer => |inner| try stringify(inner, options, out_stream),
71 .float => |inner| try stringify(inner, options, out_stream),
72 .number_string => |inner| try out_stream.writeAll(inner),
73 .string => |inner| try stringify(inner, options, out_stream),
74 .array => |inner| try stringify(inner.items, options, out_stream),
64 .null => try jws.write(null),
65 .bool => |inner| try jws.write(inner),
66 .integer => |inner| try jws.write(inner),
67 .float => |inner| try jws.write(inner),
68 .number_string => |inner| try jws.writePreformatted(inner),
69 .string => |inner| try jws.write(inner),
70 .array => |inner| try jws.write(inner.items),
7571 .object => |inner| {
76 try out_stream.writeByte('{');
77 var field_output = false;
78 var child_options = options;
79 child_options.whitespace.indent_level += 1;
72 try jws.beginObject();
8073 var it = inner.iterator();
8174 while (it.next()) |entry| {
82 if (!field_output) {
83 field_output = true;
84 } else {
85 try out_stream.writeByte(',');
86 }
87 try child_options.whitespace.outputIndent(out_stream);
88
89 try stringify(entry.key_ptr.*, options, out_stream);
90 try out_stream.writeByte(':');
91 if (child_options.whitespace.separator) {
92 try out_stream.writeByte(' ');
93 }
94 try stringify(entry.value_ptr.*, child_options, out_stream);
95 }
96 if (field_output) {
97 try options.whitespace.outputIndent(out_stream);
75 try jws.objectField(entry.key_ptr.*);
76 try jws.write(entry.value_ptr.*);
9877 }
99 try out_stream.writeByte('}');
78 try jws.endObject();
10079 },
10180 }
10281 }
lib/std/json/dynamic_test.zig+53-71
......@@ -69,38 +69,34 @@ test "json.parser.dynamic" {
6969 try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615"));
7070}
7171
72const writeStream = @import("./write_stream.zig").writeStream;
72const writeStream = @import("./stringify.zig").writeStream;
7373test "write json then parse it" {
7474 var out_buffer: [1000]u8 = undefined;
7575
7676 var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer);
7777 const out_stream = fixed_buffer_stream.writer();
78 var jw = writeStream(out_stream, 4);
78 var jw = writeStream(out_stream, .{});
79 defer jw.deinit();
7980
8081 try jw.beginObject();
8182
8283 try jw.objectField("f");
83 try jw.emitBool(false);
84 try jw.write(false);
8485
8586 try jw.objectField("t");
86 try jw.emitBool(true);
87 try jw.write(true);
8788
8889 try jw.objectField("int");
89 try jw.emitNumber(1234);
90 try jw.write(1234);
9091
9192 try jw.objectField("array");
9293 try jw.beginArray();
93
94 try jw.arrayElem();
95 try jw.emitNull();
96
97 try jw.arrayElem();
98 try jw.emitNumber(12.34);
99
94 try jw.write(null);
95 try jw.write(12.34);
10096 try jw.endArray();
10197
10298 try jw.objectField("str");
103 try jw.emitString("hello");
99 try jw.write("hello");
104100
105101 try jw.endObject();
106102
......@@ -185,64 +181,50 @@ test "escaped characters" {
185181}
186182
187183test "Value.jsonStringify" {
188 {
189 var buffer: [10]u8 = undefined;
190 var fbs = std.io.fixedBufferStream(&buffer);
191 try @as(Value, .null).jsonStringify(.{}, fbs.writer());
192 try testing.expectEqualSlices(u8, fbs.getWritten(), "null");
193 }
194 {
195 var buffer: [10]u8 = undefined;
196 var fbs = std.io.fixedBufferStream(&buffer);
197 try (Value{ .bool = true }).jsonStringify(.{}, fbs.writer());
198 try testing.expectEqualSlices(u8, fbs.getWritten(), "true");
199 }
200 {
201 var buffer: [10]u8 = undefined;
202 var fbs = std.io.fixedBufferStream(&buffer);
203 try (Value{ .integer = 42 }).jsonStringify(.{}, fbs.writer());
204 try testing.expectEqualSlices(u8, fbs.getWritten(), "42");
205 }
206 {
207 var buffer: [10]u8 = undefined;
208 var fbs = std.io.fixedBufferStream(&buffer);
209 try (Value{ .number_string = "43" }).jsonStringify(.{}, fbs.writer());
210 try testing.expectEqualSlices(u8, fbs.getWritten(), "43");
211 }
212 {
213 var buffer: [10]u8 = undefined;
214 var fbs = std.io.fixedBufferStream(&buffer);
215 try (Value{ .float = 42 }).jsonStringify(.{}, fbs.writer());
216 try testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01");
217 }
218 {
219 var buffer: [10]u8 = undefined;
220 var fbs = std.io.fixedBufferStream(&buffer);
221 try (Value{ .string = "weeee" }).jsonStringify(.{}, fbs.writer());
222 try testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\"");
223 }
224 {
225 var buffer: [10]u8 = undefined;
226 var fbs = std.io.fixedBufferStream(&buffer);
227 var vals = [_]Value{
228 .{ .integer = 1 },
229 .{ .integer = 2 },
230 .{ .number_string = "3" },
231 };
232 try (Value{
233 .array = Array.fromOwnedSlice(undefined, &vals),
234 }).jsonStringify(.{}, fbs.writer());
235 try testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]");
236 }
237 {
238 var buffer: [10]u8 = undefined;
239 var fbs = std.io.fixedBufferStream(&buffer);
240 var obj = ObjectMap.init(testing.allocator);
241 defer obj.deinit();
242 try obj.putNoClobber("a", .{ .string = "b" });
243 try (Value{ .object = obj }).jsonStringify(.{}, fbs.writer());
244 try testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}");
245 }
184 var vals = [_]Value{
185 .{ .integer = 1 },
186 .{ .integer = 2 },
187 .{ .number_string = "3" },
188 };
189 var obj = ObjectMap.init(testing.allocator);
190 defer obj.deinit();
191 try obj.putNoClobber("a", .{ .string = "b" });
192 var array = [_]Value{
193 Value.null,
194 Value{ .bool = true },
195 Value{ .integer = 42 },
196 Value{ .number_string = "43" },
197 Value{ .float = 42 },
198 Value{ .string = "weeee" },
199 Value{ .array = Array.fromOwnedSlice(undefined, &vals) },
200 Value{ .object = obj },
201 };
202 var buffer: [0x1000]u8 = undefined;
203 var fbs = std.io.fixedBufferStream(&buffer);
204
205 var jw = writeStream(fbs.writer(), .{ .whitespace = .indent_1 });
206 defer jw.deinit();
207 try jw.write(array);
208
209 const expected =
210 \\[
211 \\ null,
212 \\ true,
213 \\ 42,
214 \\ 43,
215 \\ 4.2e+01,
216 \\ "weeee",
217 \\ [
218 \\ 1,
219 \\ 2,
220 \\ 3
221 \\ ],
222 \\ {
223 \\ "a": "b"
224 \\ }
225 \\]
226 ;
227 try testing.expectEqualSlices(u8, expected, fbs.getWritten());
246228}
247229
248230test "parseFromValue(std.json.Value,...)" {
lib/std/json/hashmap.zig+5-24
......@@ -5,9 +5,6 @@ const ParseOptions = @import("static.zig").ParseOptions;
55const innerParse = @import("static.zig").innerParse;
66const innerParseFromValue = @import("static.zig").innerParseFromValue;
77const Value = @import("dynamic.zig").Value;
8const StringifyOptions = @import("stringify.zig").StringifyOptions;
9const stringify = @import("stringify.zig").stringify;
10const encodeJsonString = @import("stringify.zig").encodeJsonString;
118
129/// A thin wrapper around `std.StringArrayHashMapUnmanaged` that implements
1310/// `jsonParse`, `jsonParseFromValue`, and `jsonStringify`.
......@@ -70,30 +67,14 @@ pub fn ArrayHashMap(comptime T: type) type {
7067 return .{ .map = map };
7168 }
7269
73 pub fn jsonStringify(self: @This(), options: StringifyOptions, out_stream: anytype) !void {
74 try out_stream.writeByte('{');
75 var field_output = false;
76 var child_options = options;
77 child_options.whitespace.indent_level += 1;
70 pub fn jsonStringify(self: @This(), jws: anytype) !void {
71 try jws.beginObject();
7872 var it = self.map.iterator();
7973 while (it.next()) |kv| {
80 if (!field_output) {
81 field_output = true;
82 } else {
83 try out_stream.writeByte(',');
84 }
85 try child_options.whitespace.outputIndent(out_stream);
86 try encodeJsonString(kv.key_ptr.*, options, out_stream);
87 try out_stream.writeByte(':');
88 if (child_options.whitespace.separator) {
89 try out_stream.writeByte(' ');
90 }
91 try stringify(kv.value_ptr.*, child_options, out_stream);
92 }
93 if (field_output) {
94 try options.whitespace.outputIndent(out_stream);
74 try jws.objectField(kv.key_ptr.*);
75 try jws.write(kv.value_ptr.*);
9576 }
96 try out_stream.writeByte('}');
77 try jws.endObject();
9778 }
9879 };
9980}
lib/std/json/hashmap_test.zig+1-5
......@@ -101,11 +101,7 @@ test "stringify json hashmap whitespace" {
101101 try value.map.put(testing.allocator, "xyz", .{ .i = 1, .s = "w" });
102102
103103 {
104 const doc = try stringifyAlloc(testing.allocator, value, .{
105 .whitespace = .{
106 .indent = .{ .space = 2 },
107 },
108 });
104 const doc = try stringifyAlloc(testing.allocator, value, .{ .whitespace = .indent_2 });
109105 defer testing.allocator.free(doc);
110106 try testing.expectEqualStrings(
111107 \\{
lib/std/json/scanner.zig+7-53
......@@ -33,6 +33,7 @@ const std = @import("std");
3333const Allocator = std.mem.Allocator;
3434const ArrayList = std.ArrayList;
3535const assert = std.debug.assert;
36const BitStack = std.BitStack;
3637
3738/// Scan the input and check for malformed JSON.
3839/// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`.
......@@ -337,7 +338,7 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type {
337338 }
338339 }
339340 /// Like `std.json.Scanner.skipUntilStackHeight()` but handles `error.BufferUnderrun`.
340 pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: u32) NextError!void {
341 pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void {
341342 while (true) {
342343 return self.scanner.skipUntilStackHeight(terminal_stack_height) catch |err| switch (err) {
343344 error.BufferUnderrun => {
......@@ -350,11 +351,11 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type {
350351 }
351352
352353 /// Calls `std.json.Scanner.stackHeight`.
353 pub fn stackHeight(self: *const @This()) u32 {
354 pub fn stackHeight(self: *const @This()) usize {
354355 return self.scanner.stackHeight();
355356 }
356357 /// Calls `std.json.Scanner.ensureTotalStackCapacity`.
357 pub fn ensureTotalStackCapacity(self: *@This(), height: u32) Allocator.Error!void {
358 pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void {
358359 try self.scanner.ensureTotalStackCapacity(height);
359360 }
360361
......@@ -654,7 +655,7 @@ pub const Scanner = struct {
654655
655656 /// Skip tokens until an `.object_end` or `.array_end` token results in a `stackHeight()` equal the given stack height.
656657 /// Unlike `skipValue()`, this function is available in streaming mode.
657 pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: u32) NextError!void {
658 pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void {
658659 while (true) {
659660 switch (try self.next()) {
660661 .object_end, .array_end => {
......@@ -667,13 +668,13 @@ pub const Scanner = struct {
667668 }
668669
669670 /// The depth of `{}` or `[]` nesting levels at the current position.
670 pub fn stackHeight(self: *const @This()) u32 {
671 pub fn stackHeight(self: *const @This()) usize {
671672 return self.stack.bit_len;
672673 }
673674
674675 /// Pre allocate memory to hold the given number of nesting levels.
675676 /// `stackHeight()` up to the given number will not cause allocations.
676 pub fn ensureTotalStackCapacity(self: *@This(), height: u32) Allocator.Error!void {
677 pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void {
677678 try self.stack.ensureTotalCapacity(height);
678679 }
679680
......@@ -1697,53 +1698,6 @@ pub const Scanner = struct {
16971698const OBJECT_MODE = 0;
16981699const ARRAY_MODE = 1;
16991700
1700const BitStack = struct {
1701 bytes: std.ArrayList(u8),
1702 bit_len: u32 = 0,
1703
1704 pub fn init(allocator: Allocator) @This() {
1705 return .{
1706 .bytes = std.ArrayList(u8).init(allocator),
1707 };
1708 }
1709
1710 pub fn deinit(self: *@This()) void {
1711 self.bytes.deinit();
1712 self.* = undefined;
1713 }
1714
1715 pub fn ensureTotalCapacity(self: *@This(), bit_capcity: u32) Allocator.Error!void {
1716 const byte_capacity = (bit_capcity + 7) >> 3;
1717 try self.bytes.ensureTotalCapacity(byte_capacity);
1718 }
1719
1720 pub fn push(self: *@This(), b: u1) Allocator.Error!void {
1721 const byte_index = self.bit_len >> 3;
1722 const bit_index = @as(u3, @intCast(self.bit_len & 7));
1723
1724 if (self.bytes.items.len <= byte_index) {
1725 try self.bytes.append(0);
1726 }
1727
1728 self.bytes.items[byte_index] &= ~(@as(u8, 1) << bit_index);
1729 self.bytes.items[byte_index] |= @as(u8, b) << bit_index;
1730
1731 self.bit_len += 1;
1732 }
1733
1734 pub fn peek(self: *const @This()) u1 {
1735 const byte_index = (self.bit_len - 1) >> 3;
1736 const bit_index = @as(u3, @intCast((self.bit_len - 1) & 7));
1737 return @as(u1, @intCast((self.bytes.items[byte_index] >> bit_index) & 1));
1738 }
1739
1740 pub fn pop(self: *@This()) u1 {
1741 const b = self.peek();
1742 self.bit_len -= 1;
1743 return b;
1744 }
1745};
1746
17471701fn appendSlice(list: *std.ArrayList(u8), buf: []const u8, max_value_len: usize) !void {
17481702 const new_len = std.math.add(usize, list.items.len, buf.len) catch return error.ValueTooLong;
17491703 if (new_len > max_value_len) return error.ValueTooLong;
lib/std/json/stringify.zig+606-257
......@@ -1,73 +1,583 @@
11const std = @import("std");
2const mem = std.mem;
32const assert = std.debug.assert;
3const Allocator = std.mem.Allocator;
4const ArrayList = std.ArrayList;
5const BitStack = std.BitStack;
6
7const OBJECT_MODE = 0;
8const ARRAY_MODE = 1;
49
510pub const StringifyOptions = struct {
6 pub const Whitespace = struct {
7 /// How many indentation levels deep are we?
11 /// Controls the whitespace emitted.
12 /// The default `.minified` is a compact encoding with no whitespace between tokens.
13 /// Any setting other than `.minified` will use newlines, indentation, and a space after each ':'.
14 /// `.indent_1` means 1 space for each indentation level, `.indent_2` means 2 spaces, etc.
15 /// `.indent_tab` uses a tab for each indentation level.
16 whitespace: enum {
17 minified,
18 indent_1,
19 indent_2,
20 indent_3,
21 indent_4,
22 indent_8,
23 indent_tab,
24 } = .minified,
25
26 /// Should optional fields with null value be written?
27 emit_null_optional_fields: bool = true,
28
29 /// Arrays/slices of u8 are typically encoded as JSON strings.
30 /// This option emits them as arrays of numbers instead.
31 /// Does not affect calls to `objectField()`.
32 emit_strings_as_arrays: bool = false,
33
34 /// Should unicode characters be escaped in strings?
35 escape_unicode: bool = false,
36};
37
38/// Writes the given value to the `std.io.Writer` stream.
39/// See `WriteStream` for how the given value is serialized into JSON.
40/// The maximum nesting depth of the output JSON document is 256.
41/// See also `stringifyMaxDepth` and `stringifyArbitraryDepth`.
42pub fn stringify(
43 value: anytype,
44 options: StringifyOptions,
45 out_stream: anytype,
46) @TypeOf(out_stream).Error!void {
47 var jw = writeStream(out_stream, options);
48 defer jw.deinit();
49 try jw.write(value);
50}
51
52/// Like `stringify` with configurable nesting depth.
53/// `max_depth` is rounded up to the nearest multiple of 8.
54/// Give `null` for `max_depth` to disable some safety checks and allow arbitrary nesting depth.
55/// See `writeStreamMaxDepth` for more info.
56pub fn stringifyMaxDepth(
57 value: anytype,
58 options: StringifyOptions,
59 out_stream: anytype,
60 comptime max_depth: ?usize,
61) @TypeOf(out_stream).Error!void {
62 var jw = writeStreamMaxDepth(out_stream, options, max_depth);
63 try jw.write(value);
64}
65
66/// Like `stringify` but takes an allocator to facilitate safety checks while allowing arbitrary nesting depth.
67/// These safety checks can be helpful when debugging custom `jsonStringify` implementations;
68/// See `WriteStream`.
69pub fn stringifyArbitraryDepth(
70 allocator: Allocator,
71 value: anytype,
72 options: StringifyOptions,
73 out_stream: anytype,
74) WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth).Error!void {
75 var jw = writeStreamArbitraryDepth(allocator, out_stream, options);
76 defer jw.deinit();
77 try jw.write(value);
78}
79
80/// Calls `stringifyArbitraryDepth` and stores the result in dynamically allocated memory
81/// instead of taking a `std.io.Writer`.
82///
83/// Caller owns returned memory.
84pub fn stringifyAlloc(
85 allocator: Allocator,
86 value: anytype,
87 options: StringifyOptions,
88) error{OutOfMemory}![]const u8 {
89 var list = std.ArrayList(u8).init(allocator);
90 errdefer list.deinit();
91 try stringifyArbitraryDepth(allocator, value, options, list.writer());
92 return list.toOwnedSlice();
93}
94
95/// See `WriteStream` for documentation.
96/// Equivalent to calling `writeStreamMaxDepth` with a depth of `256`.
97///
98/// The caller does *not* need to call `deinit()` on the returned object.
99pub fn writeStream(
100 out_stream: anytype,
101 options: StringifyOptions,
102) WriteStream(@TypeOf(out_stream), .{ .checked_to_fixed_depth = 256 }) {
103 return writeStreamMaxDepth(out_stream, options, 256);
104}
105
106/// See `WriteStream` for documentation.
107/// The returned object includes 1 bit of size per `max_depth` to enable safety checks on the order of method calls;
108/// see the grammar in the `WriteStream` documentation.
109/// `max_depth` is rounded up to the nearest multiple of 8.
110/// If the nesting depth exceeds `max_depth`, it is detectable illegal behavior.
111/// Give `null` for `max_depth` to disable safety checks for the grammar and allow arbitrary nesting depth.
112/// Alternatively, see `writeStreamArbitraryDepth` to do safety checks to arbitrary depth.
113///
114/// The caller does *not* need to call `deinit()` on the returned object.
115pub fn writeStreamMaxDepth(
116 out_stream: anytype,
117 options: StringifyOptions,
118 comptime max_depth: ?usize,
119) WriteStream(
120 @TypeOf(out_stream),
121 if (max_depth) |d| .{ .checked_to_fixed_depth = d } else .assumed_correct,
122) {
123 return WriteStream(
124 @TypeOf(out_stream),
125 if (max_depth) |d| .{ .checked_to_fixed_depth = d } else .assumed_correct,
126 ).init(undefined, out_stream, options);
127}
128
129/// See `WriteStream` for documentation.
130/// This version of the write stream enables safety checks to arbitrarily deep nesting levels
131/// by using the given allocator.
132/// The caller should call `deinit()` on the returned object to free allocated memory.
133pub fn writeStreamArbitraryDepth(
134 allocator: Allocator,
135 out_stream: anytype,
136 options: StringifyOptions,
137) WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth) {
138 return WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth).init(allocator, out_stream, options);
139}
140
141/// Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data
142/// to a stream.
143///
144/// The seqeunce of method calls to write JSON content must follow this grammar:
145/// ```
146/// <once> = <value>
147/// <value> =
148/// | <object>
149/// | <array>
150/// | write
151/// | writePreformatted
152/// <object> = beginObject ( objectField <value> )* endObject
153/// <array> = beginArray ( <value> )* endArray
154/// ```
155///
156/// Supported types:
157/// * Zig `bool` -> JSON `true` or `false`.
158/// * Zig `?T` -> `null` or the rendering of `T`.
159/// * Zig `i32`, `u64`, etc. -> JSON number or string.
160/// * If the value is outside the range `±1<<53` (the precise integer rage of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number.
161/// * Zig floats -> JSON number or string.
162/// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number.
163/// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00".
164/// * Zig `[]const u8`, `[]u8`, `*[N]u8`, `@Vector(N, u8)`, and similar -> JSON string.
165/// * See `StringifyOptions.emit_strings_as_arrays`.
166/// * If the content is not valid UTF-8, rendered as an array of numbers instead.
167/// * Zig `[]T`, `[N]T`, `*[N]T`, `@Vector(N, T)`, and similar -> JSON array of the rendering of each item.
168/// * Zig tuple -> JSON array of the rendering of each item.
169/// * Zig `struct` -> JSON object with each field in declaration order.
170/// * If the struct declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. See `std.json.Value` for an example.
171/// * See `StringifyOptions.emit_null_optional_fields`.
172/// * Zig `union(enum)` -> JSON object with one field named for the active tag and a value representing the payload.
173/// * If the payload is `void`, then the emitted value is `{}`.
174/// * If the union declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`.
175/// * Zig `enum` -> JSON string naming the active tag.
176/// * If the enum declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`.
177/// * Zig error -> JSON string naming the error.
178/// * Zig `*T` -> the rendering of `T`. Note there is no guard against circular-reference infinite recursion.
179pub fn WriteStream(
180 comptime OutStream: type,
181 comptime safety_checks: union(enum) {
182 checked_to_arbitrary_depth,
183 checked_to_fixed_depth: usize, // Rounded up to the nearest multiple of 8.
184 assumed_correct,
185 },
186) type {
187 return struct {
188 const Self = @This();
189
190 pub const Stream = OutStream;
191 pub const Error = switch (safety_checks) {
192 .checked_to_arbitrary_depth => Stream.Error || error{OutOfMemory},
193 .checked_to_fixed_depth, .assumed_correct => Stream.Error,
194 };
195
196 options: StringifyOptions,
197
198 stream: OutStream,
8199 indent_level: usize = 0,
200 next_punctuation: enum {
201 the_beginning,
202 none,
203 comma,
204 colon,
205 } = .the_beginning,
206
207 nesting_stack: switch (safety_checks) {
208 .checked_to_arbitrary_depth => BitStack,
209 .checked_to_fixed_depth => |fixed_buffer_size| [(fixed_buffer_size + 7) >> 3]u8,
210 .assumed_correct => void,
211 },
9212
10 /// What character(s) should be used for indentation?
11 indent: union(enum) {
12 space: u8,
13 tab: void,
14 none: void,
15 } = .{ .space = 4 },
16
17 /// After a colon, should whitespace be inserted?
18 separator: bool = true,
19
20 pub fn outputIndent(
21 whitespace: @This(),
22 out_stream: anytype,
23 ) @TypeOf(out_stream).Error!void {
24 var char: u8 = undefined;
25 var n_chars: usize = undefined;
26 switch (whitespace.indent) {
27 .space => |n_spaces| {
28 char = ' ';
29 n_chars = n_spaces;
213 pub fn init(safety_allocator: Allocator, stream: OutStream, options: StringifyOptions) Self {
214 return .{
215 .options = options,
216 .stream = stream,
217 .nesting_stack = switch (safety_checks) {
218 .checked_to_arbitrary_depth => BitStack.init(safety_allocator),
219 .checked_to_fixed_depth => |fixed_buffer_size| [_]u8{0} ** ((fixed_buffer_size + 7) >> 3),
220 .assumed_correct => {},
30221 },
31 .tab => {
222 };
223 }
224
225 pub fn deinit(self: *Self) void {
226 switch (safety_checks) {
227 .checked_to_arbitrary_depth => self.nesting_stack.deinit(),
228 .checked_to_fixed_depth, .assumed_correct => {},
229 }
230 self.* = undefined;
231 }
232
233 pub fn beginArray(self: *Self) Error!void {
234 try self.valueStart();
235 try self.stream.writeByte('[');
236 try self.pushIndentation(ARRAY_MODE);
237 self.next_punctuation = .none;
238 }
239
240 pub fn beginObject(self: *Self) Error!void {
241 try self.valueStart();
242 try self.stream.writeByte('{');
243 try self.pushIndentation(OBJECT_MODE);
244 self.next_punctuation = .none;
245 }
246
247 pub fn endArray(self: *Self) Error!void {
248 self.popIndentation(ARRAY_MODE);
249 switch (self.next_punctuation) {
250 .none => {},
251 .comma => {
252 try self.indent();
253 },
254 .the_beginning, .colon => unreachable,
255 }
256 try self.stream.writeByte(']');
257 self.valueDone();
258 }
259
260 pub fn endObject(self: *Self) Error!void {
261 self.popIndentation(OBJECT_MODE);
262 switch (self.next_punctuation) {
263 .none => {},
264 .comma => {
265 try self.indent();
266 },
267 .the_beginning, .colon => unreachable,
268 }
269 try self.stream.writeByte('}');
270 self.valueDone();
271 }
272
273 fn pushIndentation(self: *Self, mode: u1) !void {
274 switch (safety_checks) {
275 .checked_to_arbitrary_depth => {
276 try self.nesting_stack.push(mode);
277 self.indent_level += 1;
278 },
279 .checked_to_fixed_depth => {
280 BitStack.pushWithStateAssumeCapacity(&self.nesting_stack, &self.indent_level, mode);
281 },
282 .assumed_correct => {
283 self.indent_level += 1;
284 },
285 }
286 }
287 fn popIndentation(self: *Self, assert_its_this_one: u1) void {
288 switch (safety_checks) {
289 .checked_to_arbitrary_depth => {
290 assert(self.nesting_stack.pop() == assert_its_this_one);
291 self.indent_level -= 1;
292 },
293 .checked_to_fixed_depth => {
294 assert(BitStack.popWithState(&self.nesting_stack, &self.indent_level) == assert_its_this_one);
295 },
296 .assumed_correct => {
297 self.indent_level -= 1;
298 },
299 }
300 }
301
302 fn indent(self: *Self) !void {
303 var char: u8 = ' ';
304 const n_chars = switch (self.options.whitespace) {
305 .minified => return,
306 .indent_1 => 1 * self.indent_level,
307 .indent_2 => 2 * self.indent_level,
308 .indent_3 => 3 * self.indent_level,
309 .indent_4 => 4 * self.indent_level,
310 .indent_8 => 8 * self.indent_level,
311 .indent_tab => blk: {
32312 char = '\t';
33 n_chars = 1;
313 break :blk self.indent_level;
314 },
315 };
316 try self.stream.writeByte('\n');
317 try self.stream.writeByteNTimes(char, n_chars);
318 }
319
320 fn valueStart(self: *Self) !void {
321 if (self.isObjectKeyExpected()) |is_it| assert(!is_it); // Call objectField(), not write(), for object keys.
322 return self.valueStartAssumeTypeOk();
323 }
324 fn objectFieldStart(self: *Self) !void {
325 if (self.isObjectKeyExpected()) |is_it| assert(is_it); // Expected write(), not objectField().
326 return self.valueStartAssumeTypeOk();
327 }
328 fn valueStartAssumeTypeOk(self: *Self) !void {
329 assert(!self.isComplete()); // JSON document already complete.
330 switch (self.next_punctuation) {
331 .the_beginning => {
332 // No indentation for the very beginning.
333 },
334 .none => {
335 // First item in a container.
336 try self.indent();
337 },
338 .comma => {
339 // Subsequent item in a container.
340 try self.stream.writeByte(',');
341 try self.indent();
342 },
343 .colon => {
344 try self.stream.writeByte(':');
345 if (self.options.whitespace != .minified) {
346 try self.stream.writeByte(' ');
347 }
34348 },
35 .none => return,
36349 }
37 try out_stream.writeByte('\n');
38 n_chars *= whitespace.indent_level;
39 try out_stream.writeByteNTimes(char, n_chars);
40350 }
41 };
351 fn valueDone(self: *Self) void {
352 self.next_punctuation = .comma;
353 }
42354
43 /// Controls the whitespace emitted
44 whitespace: Whitespace = .{ .indent = .none, .separator = false },
355 // Only when safety is enabled:
356 fn isObjectKeyExpected(self: *const Self) ?bool {
357 switch (safety_checks) {
358 .checked_to_arbitrary_depth => return self.indent_level > 0 and
359 self.nesting_stack.peek() == OBJECT_MODE and
360 self.next_punctuation != .colon,
361 .checked_to_fixed_depth => return self.indent_level > 0 and
362 BitStack.peekWithState(&self.nesting_stack, self.indent_level) == OBJECT_MODE and
363 self.next_punctuation != .colon,
364 .assumed_correct => return null,
365 }
366 }
367 fn isComplete(self: *const Self) bool {
368 return self.indent_level == 0 and self.next_punctuation == .comma;
369 }
45370
46 /// Should optional fields with null value be written?
47 emit_null_optional_fields: bool = true,
371 /// An alternative to calling `write` that outputs the given bytes verbatim.
372 /// This function does the usual punctuation and indentation formatting
373 /// assuming the given slice represents a single complete value;
374 /// e.g. `"1"`, `"[]"`, `"[1,2]"`, not `"1,2"`.
375 pub fn writePreformatted(self: *Self, value_slice: []const u8) Error!void {
376 try self.valueStart();
377 try self.stream.writeAll(value_slice);
378 self.valueDone();
379 }
48380
49 string: StringOptions = StringOptions{ .String = .{} },
381 pub fn objectField(self: *Self, key: []const u8) Error!void {
382 try self.objectFieldStart();
383 try encodeJsonString(key, self.options, self.stream);
384 self.next_punctuation = .colon;
385 }
50386
51 /// Should []u8 be serialised as a string? or an array?
52 pub const StringOptions = union(enum) {
53 Array,
54 String: StringOutputOptions,
387 /// See `WriteStream`.
388 pub fn write(self: *Self, value: anytype) Error!void {
389 const T = @TypeOf(value);
390 switch (@typeInfo(T)) {
391 .Int => |info| {
392 if (info.bits < 53) {
393 try self.valueStart();
394 try self.stream.print("{}", .{value});
395 self.valueDone();
396 return;
397 }
398 if (value < 4503599627370496 and (info.signedness == .unsigned or value > -4503599627370496)) {
399 try self.valueStart();
400 try self.stream.print("{}", .{value});
401 self.valueDone();
402 return;
403 }
404 try self.valueStart();
405 try self.stream.print("\"{}\"", .{value});
406 self.valueDone();
407 return;
408 },
409 .ComptimeInt => {
410 return self.write(@as(std.math.IntFittingRange(value, value), value));
411 },
412 .Float, .ComptimeFloat => {
413 if (@as(f64, @floatCast(value)) == value) {
414 try self.valueStart();
415 try self.stream.print("{}", .{@as(f64, @floatCast(value))});
416 self.valueDone();
417 return;
418 }
419 try self.valueStart();
420 try self.stream.print("\"{}\"", .{value});
421 self.valueDone();
422 return;
423 },
55424
56 /// String output options
57 const StringOutputOptions = struct {
58 /// Should '/' be escaped in strings?
59 escape_solidus: bool = false,
425 .Bool => {
426 try self.valueStart();
427 try self.stream.writeAll(if (value) "true" else "false");
428 self.valueDone();
429 return;
430 },
431 .Null => {
432 try self.valueStart();
433 try self.stream.writeAll("null");
434 self.valueDone();
435 return;
436 },
437 .Optional => {
438 if (value) |payload| {
439 return try self.write(payload);
440 } else {
441 return try self.write(null);
442 }
443 },
444 .Enum => {
445 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
446 return value.jsonStringify(self);
447 }
60448
61 /// Should unicode characters be escaped in strings?
62 escape_unicode: bool = false,
63 };
449 return self.stringValue(@tagName(value));
450 },
451 .Union => {
452 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
453 return value.jsonStringify(self);
454 }
455
456 const info = @typeInfo(T).Union;
457 if (info.tag_type) |UnionTagType| {
458 try self.beginObject();
459 inline for (info.fields) |u_field| {
460 if (value == @field(UnionTagType, u_field.name)) {
461 try self.objectField(u_field.name);
462 if (u_field.type == void) {
463 // void value is {}
464 try self.beginObject();
465 try self.endObject();
466 } else {
467 try self.write(@field(value, u_field.name));
468 }
469 break;
470 }
471 } else {
472 unreachable; // No active tag?
473 }
474 try self.endObject();
475 return;
476 } else {
477 @compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'");
478 }
479 },
480 .Struct => |S| {
481 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
482 return value.jsonStringify(self);
483 }
484
485 if (S.is_tuple) {
486 try self.beginArray();
487 } else {
488 try self.beginObject();
489 }
490 inline for (S.fields) |Field| {
491 // don't include void fields
492 if (Field.type == void) continue;
493
494 var emit_field = true;
495
496 // don't include optional fields that are null when emit_null_optional_fields is set to false
497 if (@typeInfo(Field.type) == .Optional) {
498 if (self.options.emit_null_optional_fields == false) {
499 if (@field(value, Field.name) == null) {
500 emit_field = false;
501 }
502 }
503 }
504
505 if (emit_field) {
506 if (!S.is_tuple) {
507 try self.objectField(Field.name);
508 }
509 try self.write(@field(value, Field.name));
510 }
511 }
512 if (S.is_tuple) {
513 try self.endArray();
514 } else {
515 try self.endObject();
516 }
517 return;
518 },
519 .ErrorSet => return self.stringValue(@errorName(value)),
520 .Pointer => |ptr_info| switch (ptr_info.size) {
521 .One => switch (@typeInfo(ptr_info.child)) {
522 .Array => {
523 // Coerce `*[N]T` to `[]const T`.
524 const Slice = []const std.meta.Elem(ptr_info.child);
525 return self.write(@as(Slice, value));
526 },
527 else => {
528 return self.write(value.*);
529 },
530 },
531 .Many, .Slice => {
532 if (ptr_info.size == .Many and ptr_info.sentinel == null)
533 @compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel");
534 const slice = if (ptr_info.size == .Many) std.mem.span(value) else value;
535
536 if (ptr_info.child == u8) {
537 // This is a []const u8, or some similar Zig string.
538 if (!self.options.emit_strings_as_arrays and std.unicode.utf8ValidateSlice(slice)) {
539 return self.stringValue(slice);
540 }
541 }
542
543 try self.beginArray();
544 for (slice) |x| {
545 try self.write(x);
546 }
547 try self.endArray();
548 return;
549 },
550 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
551 },
552 .Array => {
553 // Coerce `[N]T` to `*const [N]T` (and then to `[]const T`).
554 return self.write(&value);
555 },
556 .Vector => |info| {
557 const array: [info.len]info.child = value;
558 return self.write(&array);
559 },
560 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
561 }
562 unreachable;
563 }
564
565 fn stringValue(self: *Self, s: []const u8) !void {
566 try self.valueStart();
567 try encodeJsonString(s, self.options, self.stream);
568 self.valueDone();
569 }
570
571 pub const arrayElem = @compileError("Deprecated; You don't need to call this anymore.");
572 pub const emitNull = @compileError("Deprecated; Use .write(null) instead.");
573 pub const emitBool = @compileError("Deprecated; Use .write() instead.");
574 pub const emitNumber = @compileError("Deprecated; Use .write() instead.");
575 pub const emitString = @compileError("Deprecated; Use .write() instead.");
576 pub const emitJson = @compileError("Deprecated; Use .write() instead.");
64577 };
65};
578}
66579
67fn outputUnicodeEscape(
68 codepoint: u21,
69 out_stream: anytype,
70) !void {
580fn outputUnicodeEscape(codepoint: u21, out_stream: anytype) !void {
71581 if (codepoint <= 0xFFFF) {
72582 // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),
73583 // then it may be represented as a six-character sequence: a reverse solidus, followed
......@@ -87,6 +597,19 @@ fn outputUnicodeEscape(
87597 }
88598}
89599
600fn outputSpecialEscape(c: u8, writer: anytype) !void {
601 switch (c) {
602 '\\' => try writer.writeAll("\\\\"),
603 '\"' => try writer.writeAll("\\\""),
604 0x08 => try writer.writeAll("\\b"),
605 0x0C => try writer.writeAll("\\f"),
606 '\n' => try writer.writeAll("\\n"),
607 '\r' => try writer.writeAll("\\r"),
608 '\t' => try writer.writeAll("\\t"),
609 else => try outputUnicodeEscape(c, writer),
610 }
611}
612
90613/// Write `string` to `writer` as a JSON encoded string.
91614pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: anytype) !void {
92615 try writer.writeByte('\"');
......@@ -96,218 +619,44 @@ pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: a
96619
97620/// Write `chars` to `writer` as JSON encoded string characters.
98621pub fn encodeJsonStringChars(chars: []const u8, options: StringifyOptions, writer: anytype) !void {
622 var write_cursor: usize = 0;
99623 var i: usize = 0;
100 while (i < chars.len) : (i += 1) {
101 switch (chars[i]) {
102 // normal ascii character
103 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try writer.writeByte(c),
104 // only 2 characters that *must* be escaped
105 '\\' => try writer.writeAll("\\\\"),
106 '\"' => try writer.writeAll("\\\""),
107 // solidus is optional to escape
108 '/' => {
109 if (options.string.String.escape_solidus) {
110 try writer.writeAll("\\/");
111 } else {
112 try writer.writeByte('/');
113 }
114 },
115 // control characters with short escapes
116 // TODO: option to switch between unicode and 'short' forms?
117 0x8 => try writer.writeAll("\\b"),
118 0xC => try writer.writeAll("\\f"),
119 '\n' => try writer.writeAll("\\n"),
120 '\r' => try writer.writeAll("\\r"),
121 '\t' => try writer.writeAll("\\t"),
122 else => {
123 const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable;
124 // control characters (only things left with 1 byte length) should always be printed as unicode escapes
125 if (ulen == 1 or options.string.String.escape_unicode) {
624 if (options.escape_unicode) {
625 while (i < chars.len) : (i += 1) {
626 switch (chars[i]) {
627 // normal ascii character
628 0x20...0x21, 0x23...0x5B, 0x5D...0x7E => {},
629 0x00...0x1F, '\\', '\"' => {
630 // Always must escape these.
631 try writer.writeAll(chars[write_cursor..i]);
632 try outputSpecialEscape(chars[i], writer);
633 write_cursor = i + 1;
634 },
635 0x7F...0xFF => {
636 try writer.writeAll(chars[write_cursor..i]);
637 const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable;
126638 const codepoint = std.unicode.utf8Decode(chars[i..][0..ulen]) catch unreachable;
127639 try outputUnicodeEscape(codepoint, writer);
128 } else {
129 try writer.writeAll(chars[i..][0..ulen]);
130 }
131 i += ulen - 1;
132 },
133 }
134 }
135}
136
137/// If `value` has a method called `jsonStringify`, this will call that method instead of the
138/// default implementation, passing it the `options` and `out_stream` parameters.
139pub fn stringify(
140 value: anytype,
141 options: StringifyOptions,
142 out_stream: anytype,
143) @TypeOf(out_stream).Error!void {
144 const T = @TypeOf(value);
145 switch (@typeInfo(T)) {
146 .Float, .ComptimeFloat => {
147 return std.fmt.formatFloatScientific(value, std.fmt.FormatOptions{}, out_stream);
148 },
149 .Int, .ComptimeInt => {
150 return std.fmt.formatIntValue(value, "", std.fmt.FormatOptions{}, out_stream);
151 },
152 .Bool => {
153 return out_stream.writeAll(if (value) "true" else "false");
154 },
155 .Null => {
156 return out_stream.writeAll("null");
157 },
158 .Optional => {
159 if (value) |payload| {
160 return try stringify(payload, options, out_stream);
161 } else {
162 return try stringify(null, options, out_stream);
163 }
164 },
165 .Enum => {
166 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
167 return value.jsonStringify(options, out_stream);
168 }
169
170 return try encodeJsonString(@tagName(value), options, out_stream);
171 },
172 .Union => {
173 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
174 return value.jsonStringify(options, out_stream);
175 }
176
177 const info = @typeInfo(T).Union;
178 if (info.tag_type) |UnionTagType| {
179 try out_stream.writeByte('{');
180 var child_options = options;
181 child_options.whitespace.indent_level += 1;
182 inline for (info.fields) |u_field| {
183 if (value == @field(UnionTagType, u_field.name)) {
184 try child_options.whitespace.outputIndent(out_stream);
185 try encodeJsonString(u_field.name, options, out_stream);
186 try out_stream.writeByte(':');
187 if (child_options.whitespace.separator) {
188 try out_stream.writeByte(' ');
189 }
190 if (u_field.type == void) {
191 try out_stream.writeAll("{}");
192 } else {
193 try stringify(@field(value, u_field.name), child_options, out_stream);
194 }
195 break;
196 }
197 } else {
198 unreachable; // No active tag?
199 }
200 try options.whitespace.outputIndent(out_stream);
201 try out_stream.writeByte('}');
202 return;
203 } else {
204 @compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'");
205 }
206 },
207 .Struct => |S| {
208 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
209 return value.jsonStringify(options, out_stream);
210 }
211
212 try out_stream.writeByte(if (S.is_tuple) '[' else '{');
213 var field_output = false;
214 var child_options = options;
215 child_options.whitespace.indent_level += 1;
216 inline for (S.fields) |Field| {
217 // don't include void fields
218 if (Field.type == void) continue;
219
220 var emit_field = true;
221
222 // don't include optional fields that are null when emit_null_optional_fields is set to false
223 if (@typeInfo(Field.type) == .Optional) {
224 if (options.emit_null_optional_fields == false) {
225 if (@field(value, Field.name) == null) {
226 emit_field = false;
227 }
228 }
229 }
230
231 if (emit_field) {
232 if (!field_output) {
233 field_output = true;
234 } else {
235 try out_stream.writeByte(',');
236 }
237 try child_options.whitespace.outputIndent(out_stream);
238 if (!S.is_tuple) {
239 try encodeJsonString(Field.name, options, out_stream);
240 try out_stream.writeByte(':');
241 if (child_options.whitespace.separator) {
242 try out_stream.writeByte(' ');
243 }
244 }
245 try stringify(@field(value, Field.name), child_options, out_stream);
246 }
247 }
248 if (field_output) {
249 try options.whitespace.outputIndent(out_stream);
250 }
251 try out_stream.writeByte(if (S.is_tuple) ']' else '}');
252 return;
253 },
254 .ErrorSet => return stringify(@as([]const u8, @errorName(value)), options, out_stream),
255 .Pointer => |ptr_info| switch (ptr_info.size) {
256 .One => switch (@typeInfo(ptr_info.child)) {
257 .Array => {
258 const Slice = []const std.meta.Elem(ptr_info.child);
259 return stringify(@as(Slice, value), options, out_stream);
640 i += ulen - 1;
641 write_cursor = i + 1;
260642 },
261 else => {
262 // TODO: avoid loops?
263 return stringify(value.*, options, out_stream);
643 }
644 }
645 } else {
646 while (i < chars.len) : (i += 1) {
647 switch (chars[i]) {
648 // normal bytes
649 0x20...0x21, 0x23...0x5B, 0x5D...0xFF => {},
650 0x00...0x1F, '\\', '\"' => {
651 // Always must escape these.
652 try writer.writeAll(chars[write_cursor..i]);
653 try outputSpecialEscape(chars[i], writer);
654 write_cursor = i + 1;
264655 },
265 },
266 .Many, .Slice => {
267 if (ptr_info.size == .Many and ptr_info.sentinel == null)
268 @compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel");
269 const slice = if (ptr_info.size == .Many) mem.span(value) else value;
270
271 if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(slice)) {
272 try encodeJsonString(slice, options, out_stream);
273 return;
274 }
275
276 try out_stream.writeByte('[');
277 var child_options = options;
278 child_options.whitespace.indent_level += 1;
279 for (slice, 0..) |x, i| {
280 if (i != 0) {
281 try out_stream.writeByte(',');
282 }
283 try child_options.whitespace.outputIndent(out_stream);
284 try stringify(x, child_options, out_stream);
285 }
286 if (slice.len != 0) {
287 try options.whitespace.outputIndent(out_stream);
288 }
289 try out_stream.writeByte(']');
290 return;
291 },
292 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
293 },
294 .Array => return stringify(&value, options, out_stream),
295 .Vector => |info| {
296 const array: [info.len]info.child = value;
297 return stringify(&array, options, out_stream);
298 },
299 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
656 }
657 }
300658 }
301 unreachable;
302}
303
304// Same as `stringify` but accepts an Allocator and stores result in dynamically allocated memory instead of using a Writer.
305// Caller owns returned memory.
306pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: StringifyOptions) ![]const u8 {
307 var list = std.ArrayList(u8).init(allocator);
308 errdefer list.deinit();
309 try stringify(value, options, list.writer());
310 return list.toOwnedSlice();
659 try writer.writeAll(chars[write_cursor..chars.len]);
311660}
312661
313662test {
lib/std/json/stringify_test.zig+250-88
......@@ -2,9 +2,99 @@ const std = @import("std");
22const mem = std.mem;
33const testing = std.testing;
44
5const ObjectMap = @import("dynamic.zig").ObjectMap;
6const Value = @import("dynamic.zig").Value;
7
58const StringifyOptions = @import("stringify.zig").StringifyOptions;
69const stringify = @import("stringify.zig").stringify;
10const stringifyMaxDepth = @import("stringify.zig").stringifyMaxDepth;
11const stringifyArbitraryDepth = @import("stringify.zig").stringifyArbitraryDepth;
712const stringifyAlloc = @import("stringify.zig").stringifyAlloc;
13const writeStream = @import("stringify.zig").writeStream;
14const writeStreamMaxDepth = @import("stringify.zig").writeStreamMaxDepth;
15const writeStreamArbitraryDepth = @import("stringify.zig").writeStreamArbitraryDepth;
16
17test "json write stream" {
18 var out_buf: [1024]u8 = undefined;
19 var slice_stream = std.io.fixedBufferStream(&out_buf);
20 const out = slice_stream.writer();
21
22 {
23 var w = writeStream(out, .{ .whitespace = .indent_2 });
24 try testBasicWriteStream(&w, &slice_stream);
25 }
26
27 {
28 var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, 8);
29 try testBasicWriteStream(&w, &slice_stream);
30 }
31
32 {
33 var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, null);
34 try testBasicWriteStream(&w, &slice_stream);
35 }
36
37 {
38 var w = writeStreamArbitraryDepth(testing.allocator, out, .{ .whitespace = .indent_2 });
39 defer w.deinit();
40 try testBasicWriteStream(&w, &slice_stream);
41 }
42}
43
44fn testBasicWriteStream(w: anytype, slice_stream: anytype) !void {
45 slice_stream.reset();
46
47 try w.beginObject();
48
49 try w.objectField("object");
50 var arena_allocator = std.heap.ArenaAllocator.init(testing.allocator);
51 defer arena_allocator.deinit();
52 try w.write(try getJsonObject(arena_allocator.allocator()));
53
54 try w.objectField("string");
55 try w.write("This is a string");
56
57 try w.objectField("array");
58 try w.beginArray();
59 try w.write("Another string");
60 try w.write(@as(i32, 1));
61 try w.write(@as(f32, 3.5));
62 try w.endArray();
63
64 try w.objectField("int");
65 try w.write(@as(i32, 10));
66
67 try w.objectField("float");
68 try w.write(@as(f32, 3.5));
69
70 try w.endObject();
71
72 const result = slice_stream.getWritten();
73 const expected =
74 \\{
75 \\ "object": {
76 \\ "one": 1,
77 \\ "two": 2.0e+00
78 \\ },
79 \\ "string": "This is a string",
80 \\ "array": [
81 \\ "Another string",
82 \\ 1,
83 \\ 3.5e+00
84 \\ ],
85 \\ "int": 10,
86 \\ "float": 3.5e+00
87 \\}
88 ;
89 try std.testing.expectEqualStrings(expected, result);
90}
91
92fn getJsonObject(allocator: std.mem.Allocator) !Value {
93 var value = Value{ .object = ObjectMap.init(allocator) };
94 try value.object.put("one", Value{ .integer = @as(i64, @intCast(1)) });
95 try value.object.put("two", Value{ .float = 2.0 });
96 return value;
97}
898
999test "stringify null optional fields" {
10100 const MyStruct = struct {
......@@ -13,64 +103,63 @@ test "stringify null optional fields" {
13103 another_optional: ?[]const u8 = null,
14104 another_required: []const u8 = "something else",
15105 };
16 try teststringify(
106 try testStringify(
17107 \\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"}
18108 ,
19109 MyStruct{},
20 StringifyOptions{},
110 .{},
21111 );
22 try teststringify(
112 try testStringify(
23113 \\{"required":"something","another_required":"something else"}
24114 ,
25115 MyStruct{},
26 StringifyOptions{ .emit_null_optional_fields = false },
116 .{ .emit_null_optional_fields = false },
27117 );
28118}
29119
30120test "stringify basic types" {
31 try teststringify("false", false, StringifyOptions{});
32 try teststringify("true", true, StringifyOptions{});
33 try teststringify("null", @as(?u8, null), StringifyOptions{});
34 try teststringify("null", @as(?*u32, null), StringifyOptions{});
35 try teststringify("42", 42, StringifyOptions{});
36 try teststringify("4.2e+01", 42.0, StringifyOptions{});
37 try teststringify("42", @as(u8, 42), StringifyOptions{});
38 try teststringify("42", @as(u128, 42), StringifyOptions{});
39 try teststringify("4.2e+01", @as(f32, 42), StringifyOptions{});
40 try teststringify("4.2e+01", @as(f64, 42), StringifyOptions{});
41 try teststringify("\"ItBroke\"", @as(anyerror, error.ItBroke), StringifyOptions{});
121 try testStringify("false", false, .{});
122 try testStringify("true", true, .{});
123 try testStringify("null", @as(?u8, null), .{});
124 try testStringify("null", @as(?*u32, null), .{});
125 try testStringify("42", 42, .{});
126 try testStringify("4.2e+01", 42.0, .{});
127 try testStringify("42", @as(u8, 42), .{});
128 try testStringify("42", @as(u128, 42), .{});
129 try testStringify("4.2e+01", @as(f32, 42), .{});
130 try testStringify("4.2e+01", @as(f64, 42), .{});
131 try testStringify("\"ItBroke\"", @as(anyerror, error.ItBroke), .{});
132 try testStringify("\"ItBroke\"", error.ItBroke, .{});
42133}
43134
44135test "stringify string" {
45 try teststringify("\"hello\"", "hello", StringifyOptions{});
46 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{});
47 try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
48 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{});
49 try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
50 try teststringify("\"with unicode\u{80}\"", "with unicode\u{80}", StringifyOptions{});
51 try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
52 try teststringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", StringifyOptions{});
53 try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
54 try teststringify("\"with unicode\u{100}\"", "with unicode\u{100}", StringifyOptions{});
55 try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
56 try teststringify("\"with unicode\u{800}\"", "with unicode\u{800}", StringifyOptions{});
57 try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
58 try teststringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", StringifyOptions{});
59 try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
60 try teststringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", StringifyOptions{});
61 try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
62 try teststringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", StringifyOptions{});
63 try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
64 try teststringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", StringifyOptions{});
65 try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
66 try teststringify("\"/\"", "/", StringifyOptions{});
67 try teststringify("\"\\/\"", "/", StringifyOptions{ .string = .{ .String = .{ .escape_solidus = true } } });
136 try testStringify("\"hello\"", "hello", .{});
137 try testStringify("\"with\\nescapes\\r\"", "with\nescapes\r", .{});
138 try testStringify("\"with\\nescapes\\r\"", "with\nescapes\r", .{ .escape_unicode = true });
139 try testStringify("\"with unicode\\u0001\"", "with unicode\u{1}", .{});
140 try testStringify("\"with unicode\\u0001\"", "with unicode\u{1}", .{ .escape_unicode = true });
141 try testStringify("\"with unicode\u{80}\"", "with unicode\u{80}", .{});
142 try testStringify("\"with unicode\\u0080\"", "with unicode\u{80}", .{ .escape_unicode = true });
143 try testStringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", .{});
144 try testStringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", .{ .escape_unicode = true });
145 try testStringify("\"with unicode\u{100}\"", "with unicode\u{100}", .{});
146 try testStringify("\"with unicode\\u0100\"", "with unicode\u{100}", .{ .escape_unicode = true });
147 try testStringify("\"with unicode\u{800}\"", "with unicode\u{800}", .{});
148 try testStringify("\"with unicode\\u0800\"", "with unicode\u{800}", .{ .escape_unicode = true });
149 try testStringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", .{});
150 try testStringify("\"with unicode\\u8000\"", "with unicode\u{8000}", .{ .escape_unicode = true });
151 try testStringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", .{});
152 try testStringify("\"with unicode\\ud799\"", "with unicode\u{D799}", .{ .escape_unicode = true });
153 try testStringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", .{});
154 try testStringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", .{ .escape_unicode = true });
155 try testStringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", .{});
156 try testStringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", .{ .escape_unicode = true });
68157}
69158
70159test "stringify many-item sentinel-terminated string" {
71 try teststringify("\"hello\"", @as([*:0]const u8, "hello"), StringifyOptions{});
72 try teststringify("\"with\\nescapes\\r\"", @as([*:0]const u8, "with\nescapes\r"), StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
73 try teststringify("\"with unicode\\u0001\"", @as([*:0]const u8, "with unicode\u{1}"), StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
160 try testStringify("\"hello\"", @as([*:0]const u8, "hello"), .{});
161 try testStringify("\"with\\nescapes\\r\"", @as([*:0]const u8, "with\nescapes\r"), .{ .escape_unicode = true });
162 try testStringify("\"with unicode\\u0001\"", @as([*:0]const u8, "with unicode\u{1}"), .{ .escape_unicode = true });
74163}
75164
76165test "stringify enums" {
......@@ -78,8 +167,8 @@ test "stringify enums" {
78167 foo,
79168 bar,
80169 };
81 try teststringify("\"foo\"", E.foo, .{});
82 try teststringify("\"bar\"", E.bar, .{});
170 try testStringify("\"foo\"", E.foo, .{});
171 try testStringify("\"bar\"", E.bar, .{});
83172}
84173
85174test "stringify tagged unions" {
......@@ -88,24 +177,33 @@ test "stringify tagged unions" {
88177 foo: u32,
89178 bar: bool,
90179 };
91 try teststringify("{\"nothing\":{}}", T{ .nothing = {} }, StringifyOptions{});
92 try teststringify("{\"foo\":42}", T{ .foo = 42 }, StringifyOptions{});
93 try teststringify("{\"bar\":true}", T{ .bar = true }, StringifyOptions{});
180 try testStringify("{\"nothing\":{}}", T{ .nothing = {} }, .{});
181 try testStringify("{\"foo\":42}", T{ .foo = 42 }, .{});
182 try testStringify("{\"bar\":true}", T{ .bar = true }, .{});
94183}
95184
96185test "stringify struct" {
97 try teststringify("{\"foo\":42}", struct {
186 try testStringify("{\"foo\":42}", struct {
98187 foo: u32,
99 }{ .foo = 42 }, StringifyOptions{});
188 }{ .foo = 42 }, .{});
100189}
101190
102test "stringify struct with string as array" {
103 try teststringify("{\"foo\":\"bar\"}", .{ .foo = "bar" }, StringifyOptions{});
104 try teststringify("{\"foo\":[98,97,114]}", .{ .foo = "bar" }, StringifyOptions{ .string = .Array });
191test "emit_strings_as_arrays" {
192 // Should only affect string values, not object keys.
193 try testStringify("{\"foo\":\"bar\"}", .{ .foo = "bar" }, .{});
194 try testStringify("{\"foo\":[98,97,114]}", .{ .foo = "bar" }, .{ .emit_strings_as_arrays = true });
195 // Should *not* affect these types:
196 try testStringify("\"foo\"", @as(enum { foo, bar }, .foo), .{ .emit_strings_as_arrays = true });
197 try testStringify("\"ItBroke\"", error.ItBroke, .{ .emit_strings_as_arrays = true });
198 // Should work on these:
199 try testStringify("\"bar\"", @Vector(3, u8){ 'b', 'a', 'r' }, .{});
200 try testStringify("[98,97,114]", @Vector(3, u8){ 'b', 'a', 'r' }, .{ .emit_strings_as_arrays = true });
201 try testStringify("\"bar\"", [3]u8{ 'b', 'a', 'r' }, .{});
202 try testStringify("[98,97,114]", [3]u8{ 'b', 'a', 'r' }, .{ .emit_strings_as_arrays = true });
105203}
106204
107205test "stringify struct with indentation" {
108 try teststringify(
206 try testStringify(
109207 \\{
110208 \\ "foo": 42,
111209 \\ "bar": [
......@@ -122,12 +220,10 @@ test "stringify struct with indentation" {
122220 .foo = 42,
123221 .bar = .{ 1, 2, 3 },
124222 },
125 StringifyOptions{
126 .whitespace = .{},
127 },
223 .{ .whitespace = .indent_4 },
128224 );
129 try teststringify(
130 "{\n\t\"foo\":42,\n\t\"bar\":[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}",
225 try testStringify(
226 "{\n\t\"foo\": 42,\n\t\"bar\": [\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}",
131227 struct {
132228 foo: u32,
133229 bar: [3]u32,
......@@ -135,14 +231,9 @@ test "stringify struct with indentation" {
135231 .foo = 42,
136232 .bar = .{ 1, 2, 3 },
137233 },
138 StringifyOptions{
139 .whitespace = .{
140 .indent = .tab,
141 .separator = false,
142 },
143 },
234 .{ .whitespace = .indent_tab },
144235 );
145 try teststringify(
236 try testStringify(
146237 \\{"foo":42,"bar":[1,2,3]}
147238 ,
148239 struct {
......@@ -152,59 +243,53 @@ test "stringify struct with indentation" {
152243 .foo = 42,
153244 .bar = .{ 1, 2, 3 },
154245 },
155 StringifyOptions{
156 .whitespace = .{
157 .indent = .none,
158 .separator = false,
159 },
160 },
246 .{ .whitespace = .minified },
161247 );
162248}
163249
164250test "stringify struct with void field" {
165 try teststringify("{\"foo\":42}", struct {
251 try testStringify("{\"foo\":42}", struct {
166252 foo: u32,
167253 bar: void = {},
168 }{ .foo = 42 }, StringifyOptions{});
254 }{ .foo = 42 }, .{});
169255}
170256
171257test "stringify array of structs" {
172258 const MyStruct = struct {
173259 foo: u32,
174260 };
175 try teststringify("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{
261 try testStringify("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{
176262 MyStruct{ .foo = 42 },
177263 MyStruct{ .foo = 100 },
178264 MyStruct{ .foo = 1000 },
179 }, StringifyOptions{});
265 }, .{});
180266}
181267
182268test "stringify struct with custom stringifier" {
183 try teststringify("[\"something special\",42]", struct {
269 try testStringify("[\"something special\",42]", struct {
184270 foo: u32,
185271 const Self = @This();
186 pub fn jsonStringify(
187 value: Self,
188 options: StringifyOptions,
189 out_stream: anytype,
190 ) !void {
272 pub fn jsonStringify(value: @This(), jws: anytype) !void {
191273 _ = value;
192 try out_stream.writeAll("[\"something special\",");
193 try stringify(42, options, out_stream);
194 try out_stream.writeByte(']');
274 try jws.beginArray();
275 try jws.write("something special");
276 try jws.write(42);
277 try jws.endArray();
195278 }
196 }{ .foo = 42 }, StringifyOptions{});
279 }{ .foo = 42 }, .{});
197280}
198281
199282test "stringify vector" {
200 try teststringify("[1,1]", @as(@Vector(2, u32), @splat(1)), StringifyOptions{});
283 try testStringify("[1,1]", @as(@Vector(2, u32), @splat(1)), .{});
284 try testStringify("\"AA\"", @as(@Vector(2, u8), @splat('A')), .{});
285 try testStringify("[65,65]", @as(@Vector(2, u8), @splat('A')), .{ .emit_strings_as_arrays = true });
201286}
202287
203288test "stringify tuple" {
204 try teststringify("[\"foo\",42]", std.meta.Tuple(&.{ []const u8, usize }){ "foo", 42 }, StringifyOptions{});
289 try testStringify("[\"foo\",42]", std.meta.Tuple(&.{ []const u8, usize }){ "foo", 42 }, .{});
205290}
206291
207fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void {
292fn testStringify(expected: []const u8, value: anytype, options: StringifyOptions) !void {
208293 const ValidationWriter = struct {
209294 const Self = @This();
210295 pub const Writer = std.io.Writer(*Self, Error, write);
......@@ -256,8 +341,34 @@ fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions
256341 };
257342
258343 var vos = ValidationWriter.init(expected);
259 try stringify(value, options, vos.writer());
344 try stringifyArbitraryDepth(testing.allocator, value, options, vos.writer());
260345 if (vos.expected_remaining.len > 0) return error.NotEnoughData;
346
347 // Also test with safety disabled.
348 try testStringifyMaxDepth(expected, value, options, null);
349 try testStringifyArbitraryDepth(expected, value, options);
350}
351
352fn testStringifyMaxDepth(expected: []const u8, value: anytype, options: StringifyOptions, comptime max_depth: ?usize) !void {
353 var out_buf: [1024]u8 = undefined;
354 var slice_stream = std.io.fixedBufferStream(&out_buf);
355 const out = slice_stream.writer();
356
357 try stringifyMaxDepth(value, options, out, max_depth);
358 const got = slice_stream.getWritten();
359
360 try testing.expectEqualStrings(expected, got);
361}
362
363fn testStringifyArbitraryDepth(expected: []const u8, value: anytype, options: StringifyOptions) !void {
364 var out_buf: [1024]u8 = undefined;
365 var slice_stream = std.io.fixedBufferStream(&out_buf);
366 const out = slice_stream.writer();
367
368 try stringifyArbitraryDepth(testing.allocator, value, options, out);
369 const got = slice_stream.getWritten();
370
371 try testing.expectEqualStrings(expected, got);
261372}
262373
263374test "stringify alloc" {
......@@ -270,3 +381,54 @@ test "stringify alloc" {
270381
271382 try std.testing.expectEqualStrings(expected, actual);
272383}
384
385test "comptime stringify" {
386 comptime testStringifyMaxDepth("false", false, .{}, null) catch unreachable;
387 comptime testStringifyMaxDepth("false", false, .{}, 0) catch unreachable;
388 comptime testStringifyArbitraryDepth("false", false, .{}) catch unreachable;
389
390 const MyStruct = struct {
391 foo: u32,
392 };
393 comptime testStringifyMaxDepth("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{
394 MyStruct{ .foo = 42 },
395 MyStruct{ .foo = 100 },
396 MyStruct{ .foo = 1000 },
397 }, .{}, null) catch unreachable;
398 comptime testStringifyMaxDepth("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{
399 MyStruct{ .foo = 42 },
400 MyStruct{ .foo = 100 },
401 MyStruct{ .foo = 1000 },
402 }, .{}, 8) catch unreachable;
403}
404
405test "writePreformatted" {
406 var out_buf: [1024]u8 = undefined;
407 var slice_stream = std.io.fixedBufferStream(&out_buf);
408 const out = slice_stream.writer();
409
410 var w = writeStream(out, .{ .whitespace = .indent_2 });
411 defer w.deinit();
412
413 try w.beginObject();
414 try w.objectField("a");
415 try w.writePreformatted("[ ]");
416 try w.objectField("b");
417 try w.beginArray();
418 try w.writePreformatted("[[]] ");
419 try w.writePreformatted(" {}");
420 try w.endArray();
421 try w.endObject();
422
423 const result = slice_stream.getWritten();
424 const expected =
425 \\{
426 \\ "a": [ ],
427 \\ "b": [
428 \\ [[]] ,
429 \\ {}
430 \\ ]
431 \\}
432 ;
433 try std.testing.expectEqualStrings(expected, result);
434}
lib/std/json/test.zig+4-4
......@@ -4,6 +4,7 @@ const parseFromSlice = @import("./static.zig").parseFromSlice;
44const validate = @import("./scanner.zig").validate;
55const JsonScanner = @import("./scanner.zig").Scanner;
66const Value = @import("./dynamic.zig").Value;
7const stringifyAlloc = @import("./stringify.zig").stringifyAlloc;
78
89// Support for JSONTestSuite.zig
910pub fn ok(s: []const u8) !void {
......@@ -49,11 +50,10 @@ fn roundTrip(s: []const u8) !void {
4950 var parsed = try parseFromSlice(Value, testing.allocator, s, .{});
5051 defer parsed.deinit();
5152
52 var buf: [256]u8 = undefined;
53 var fbs = std.io.fixedBufferStream(&buf);
54 try parsed.value.jsonStringify(.{}, fbs.writer());
53 const rendered = try stringifyAlloc(testing.allocator, parsed.value, .{});
54 defer testing.allocator.free(rendered);
5555
56 try testing.expectEqualStrings(s, fbs.getWritten());
56 try testing.expectEqualStrings(s, rendered);
5757}
5858
5959test "truncated UTF-8 sequence" {
lib/std/json/write_stream.zig deleted-300
......@@ -1,300 +0,0 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const maxInt = std.math.maxInt;
4
5const StringifyOptions = @import("./stringify.zig").StringifyOptions;
6const jsonStringify = @import("./stringify.zig").stringify;
7
8const Value = @import("./dynamic.zig").Value;
9
10const State = enum {
11 complete,
12 value,
13 array_start,
14 array,
15 object_start,
16 object,
17};
18
19/// Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data
20/// to a stream. `max_depth` is a comptime-known upper bound on the nesting depth.
21/// TODO A future iteration of this API will allow passing `null` for this value,
22/// and disable safety checks in release builds.
23pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
24 return struct {
25 const Self = @This();
26
27 pub const Stream = OutStream;
28
29 whitespace: StringifyOptions.Whitespace = StringifyOptions.Whitespace{
30 .indent_level = 0,
31 .indent = .{ .space = 1 },
32 },
33
34 stream: OutStream,
35 state_index: usize,
36 state: [max_depth]State,
37
38 pub fn init(stream: OutStream) Self {
39 var self = Self{
40 .stream = stream,
41 .state_index = 1,
42 .state = undefined,
43 };
44 self.state[0] = .complete;
45 self.state[1] = .value;
46 return self;
47 }
48
49 pub fn beginArray(self: *Self) !void {
50 assert(self.state[self.state_index] == State.value); // need to call arrayElem or objectField
51 try self.stream.writeByte('[');
52 self.state[self.state_index] = State.array_start;
53 self.whitespace.indent_level += 1;
54 }
55
56 pub fn beginObject(self: *Self) !void {
57 assert(self.state[self.state_index] == State.value); // need to call arrayElem or objectField
58 try self.stream.writeByte('{');
59 self.state[self.state_index] = State.object_start;
60 self.whitespace.indent_level += 1;
61 }
62
63 pub fn arrayElem(self: *Self) !void {
64 const state = self.state[self.state_index];
65 switch (state) {
66 .complete => unreachable,
67 .value => unreachable,
68 .object_start => unreachable,
69 .object => unreachable,
70 .array, .array_start => {
71 if (state == .array) {
72 try self.stream.writeByte(',');
73 }
74 self.state[self.state_index] = .array;
75 self.pushState(.value);
76 try self.indent();
77 },
78 }
79 }
80
81 pub fn objectField(self: *Self, name: []const u8) !void {
82 const state = self.state[self.state_index];
83 switch (state) {
84 .complete => unreachable,
85 .value => unreachable,
86 .array_start => unreachable,
87 .array => unreachable,
88 .object, .object_start => {
89 if (state == .object) {
90 try self.stream.writeByte(',');
91 }
92 self.state[self.state_index] = .object;
93 self.pushState(.value);
94 try self.indent();
95 try self.writeEscapedString(name);
96 try self.stream.writeByte(':');
97 if (self.whitespace.separator) {
98 try self.stream.writeByte(' ');
99 }
100 },
101 }
102 }
103
104 pub fn endArray(self: *Self) !void {
105 switch (self.state[self.state_index]) {
106 .complete => unreachable,
107 .value => unreachable,
108 .object_start => unreachable,
109 .object => unreachable,
110 .array_start => {
111 self.whitespace.indent_level -= 1;
112 try self.stream.writeByte(']');
113 self.popState();
114 },
115 .array => {
116 self.whitespace.indent_level -= 1;
117 try self.indent();
118 self.popState();
119 try self.stream.writeByte(']');
120 },
121 }
122 }
123
124 pub fn endObject(self: *Self) !void {
125 switch (self.state[self.state_index]) {
126 .complete => unreachable,
127 .value => unreachable,
128 .array_start => unreachable,
129 .array => unreachable,
130 .object_start => {
131 self.whitespace.indent_level -= 1;
132 try self.stream.writeByte('}');
133 self.popState();
134 },
135 .object => {
136 self.whitespace.indent_level -= 1;
137 try self.indent();
138 self.popState();
139 try self.stream.writeByte('}');
140 },
141 }
142 }
143
144 pub fn emitNull(self: *Self) !void {
145 assert(self.state[self.state_index] == State.value);
146 try self.stringify(null);
147 self.popState();
148 }
149
150 pub fn emitBool(self: *Self, value: bool) !void {
151 assert(self.state[self.state_index] == State.value);
152 try self.stringify(value);
153 self.popState();
154 }
155
156 pub fn emitNumber(
157 self: *Self,
158 /// An integer, float, or `std.math.BigInt`. Emitted as a bare number if it fits losslessly
159 /// in a IEEE 754 double float, otherwise emitted as a string to the full precision.
160 value: anytype,
161 ) !void {
162 assert(self.state[self.state_index] == State.value);
163 switch (@typeInfo(@TypeOf(value))) {
164 .Int => |info| {
165 if (info.bits < 53) {
166 try self.stream.print("{}", .{value});
167 self.popState();
168 return;
169 }
170 if (value < 4503599627370496 and (info.signedness == .unsigned or value > -4503599627370496)) {
171 try self.stream.print("{}", .{value});
172 self.popState();
173 return;
174 }
175 },
176 .ComptimeInt => {
177 return self.emitNumber(@as(std.math.IntFittingRange(value, value), value));
178 },
179 .Float, .ComptimeFloat => if (@as(f64, @floatCast(value)) == value) {
180 try self.stream.print("{}", .{@as(f64, @floatCast(value))});
181 self.popState();
182 return;
183 },
184 else => {},
185 }
186 try self.stream.print("\"{}\"", .{value});
187 self.popState();
188 }
189
190 pub fn emitString(self: *Self, string: []const u8) !void {
191 assert(self.state[self.state_index] == State.value);
192 try self.writeEscapedString(string);
193 self.popState();
194 }
195
196 fn writeEscapedString(self: *Self, string: []const u8) !void {
197 assert(std.unicode.utf8ValidateSlice(string));
198 try self.stringify(string);
199 }
200
201 /// Writes the complete json into the output stream
202 pub fn emitJson(self: *Self, value: Value) Stream.Error!void {
203 assert(self.state[self.state_index] == State.value);
204 try self.stringify(value);
205 self.popState();
206 }
207
208 fn indent(self: *Self) !void {
209 assert(self.state_index >= 1);
210 try self.whitespace.outputIndent(self.stream);
211 }
212
213 fn pushState(self: *Self, state: State) void {
214 self.state_index += 1;
215 self.state[self.state_index] = state;
216 }
217
218 fn popState(self: *Self) void {
219 self.state_index -= 1;
220 }
221
222 fn stringify(self: *Self, value: anytype) !void {
223 try jsonStringify(value, StringifyOptions{
224 .whitespace = self.whitespace,
225 }, self.stream);
226 }
227 };
228}
229
230pub fn writeStream(
231 out_stream: anytype,
232 comptime max_depth: usize,
233) WriteStream(@TypeOf(out_stream), max_depth) {
234 return WriteStream(@TypeOf(out_stream), max_depth).init(out_stream);
235}
236
237const ObjectMap = @import("./dynamic.zig").ObjectMap;
238
239test "json write stream" {
240 var out_buf: [1024]u8 = undefined;
241 var slice_stream = std.io.fixedBufferStream(&out_buf);
242 const out = slice_stream.writer();
243
244 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
245 defer arena_allocator.deinit();
246
247 var w = writeStream(out, 10);
248
249 try w.beginObject();
250
251 try w.objectField("object");
252 try w.emitJson(try getJsonObject(arena_allocator.allocator()));
253
254 try w.objectField("string");
255 try w.emitString("This is a string");
256
257 try w.objectField("array");
258 try w.beginArray();
259 try w.arrayElem();
260 try w.emitString("Another string");
261 try w.arrayElem();
262 try w.emitNumber(@as(i32, 1));
263 try w.arrayElem();
264 try w.emitNumber(@as(f32, 3.5));
265 try w.endArray();
266
267 try w.objectField("int");
268 try w.emitNumber(@as(i32, 10));
269
270 try w.objectField("float");
271 try w.emitNumber(@as(f32, 3.5));
272
273 try w.endObject();
274
275 const result = slice_stream.getWritten();
276 const expected =
277 \\{
278 \\ "object": {
279 \\ "one": 1,
280 \\ "two": 2.0e+00
281 \\ },
282 \\ "string": "This is a string",
283 \\ "array": [
284 \\ "Another string",
285 \\ 1,
286 \\ 3.5e+00
287 \\ ],
288 \\ "int": 10,
289 \\ "float": 3.5e+00
290 \\}
291 ;
292 try std.testing.expect(std.mem.eql(u8, expected, result));
293}
294
295fn getJsonObject(allocator: std.mem.Allocator) !Value {
296 var value = Value{ .object = ObjectMap.init(allocator) };
297 try value.object.put("one", Value{ .integer = @as(i64, @intCast(1)) });
298 try value.object.put("two", Value{ .float = 2.0 });
299 return value;
300}
lib/std/std.zig+1
......@@ -8,6 +8,7 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;
88pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
99pub const AutoHashMap = hash_map.AutoHashMap;
1010pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
11pub const BitStack = @import("BitStack.zig");
1112pub const BoundedArray = @import("bounded_array.zig").BoundedArray;
1213pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned;
1314pub const Build = @import("Build.zig");
src/Autodoc.zig+34-95
......@@ -385,10 +385,11 @@ pub fn generateZirData(self: *Autodoc) !void {
385385 \\ /** @type {{DocData}} */
386386 \\ var zigAnalysis=
387387 , .{});
388 try std.json.stringify(
388 try std.json.stringifyArbitraryDepth(
389 arena_allocator.allocator(),
389390 data,
390391 .{
391 .whitespace = .{ .indent = .none, .separator = false },
392 .whitespace = .minified,
392393 .emit_null_optional_fields = true,
393394 },
394395 out,
......@@ -532,28 +533,16 @@ const DocData = struct {
532533 ret: Expr,
533534 };
534535
535 pub fn jsonStringify(
536 self: DocData,
537 opts: std.json.StringifyOptions,
538 w: anytype,
539 ) !void {
540 var jsw = std.json.writeStream(w, 15);
541 jsw.whitespace = opts.whitespace;
536 pub fn jsonStringify(self: DocData, jsw: anytype) !void {
542537 try jsw.beginObject();
543538 inline for (comptime std.meta.tags(std.meta.FieldEnum(DocData))) |f| {
544539 const f_name = @tagName(f);
545540 try jsw.objectField(f_name);
546541 switch (f) {
547 .files => try writeFileTableToJson(self.files, self.modules, &jsw),
548 .guide_sections => try writeGuidesToJson(self.guide_sections, &jsw),
549 .modules => {
550 try std.json.stringify(self.modules.values(), opts, w);
551 jsw.state_index -= 1;
552 },
553 else => {
554 try std.json.stringify(@field(self, f_name), opts, w);
555 jsw.state_index -= 1;
556 },
542 .files => try writeFileTableToJson(self.files, self.modules, jsw),
543 .guide_sections => try writeGuidesToJson(self.guide_sections, jsw),
544 .modules => try jsw.write(self.modules.values()),
545 else => try jsw.write(@field(self, f_name)),
557546 }
558547 }
559548 try jsw.endObject();
......@@ -583,24 +572,14 @@ const DocData = struct {
583572 value: usize,
584573 };
585574
586 pub fn jsonStringify(
587 self: DocModule,
588 opts: std.json.StringifyOptions,
589 w: anytype,
590 ) !void {
591 var jsw = std.json.writeStream(w, 15);
592 jsw.whitespace = opts.whitespace;
593
575 pub fn jsonStringify(self: DocModule, jsw: anytype) !void {
594576 try jsw.beginObject();
595577 inline for (comptime std.meta.tags(std.meta.FieldEnum(DocModule))) |f| {
596578 const f_name = @tagName(f);
597579 try jsw.objectField(f_name);
598580 switch (f) {
599 .table => try writeModuleTableToJson(self.table, &jsw),
600 else => {
601 try std.json.stringify(@field(self, f_name), opts, w);
602 jsw.state_index -= 1;
603 },
581 .table => try writeModuleTableToJson(self.table, jsw),
582 else => try jsw.write(@field(self, f_name)),
604583 }
605584 }
606585 try jsw.endObject();
......@@ -617,18 +596,10 @@ const DocData = struct {
617596 is_uns: bool = false, // usingnamespace
618597 parent_container: ?usize, // index into `types`
619598
620 pub fn jsonStringify(
621 self: Decl,
622 opts: std.json.StringifyOptions,
623 w: anytype,
624 ) !void {
625 var jsw = std.json.writeStream(w, 15);
626 jsw.whitespace = opts.whitespace;
599 pub fn jsonStringify(self: Decl, jsw: anytype) !void {
627600 try jsw.beginArray();
628601 inline for (comptime std.meta.fields(Decl)) |f| {
629 try jsw.arrayElem();
630 try std.json.stringify(@field(self, f.name), opts, w);
631 jsw.state_index -= 1;
602 try jsw.write(@field(self, f.name));
632603 }
633604 try jsw.endArray();
634605 }
......@@ -644,18 +615,10 @@ const DocData = struct {
644615 fields: ?[]usize = null, // index into astNodes
645616 @"comptime": bool = false,
646617
647 pub fn jsonStringify(
648 self: AstNode,
649 opts: std.json.StringifyOptions,
650 w: anytype,
651 ) !void {
652 var jsw = std.json.writeStream(w, 15);
653 jsw.whitespace = opts.whitespace;
618 pub fn jsonStringify(self: AstNode, jsw: anytype) !void {
654619 try jsw.beginArray();
655620 inline for (comptime std.meta.fields(AstNode)) |f| {
656 try jsw.arrayElem();
657 try std.json.stringify(@field(self, f.name), opts, w);
658 jsw.state_index -= 1;
621 try jsw.write(@field(self, f.name));
659622 }
660623 try jsw.endArray();
661624 }
......@@ -776,27 +739,18 @@ const DocData = struct {
776739 docs: []const u8,
777740 };
778741
779 pub fn jsonStringify(
780 self: Type,
781 opts: std.json.StringifyOptions,
782 w: anytype,
783 ) !void {
742 pub fn jsonStringify(self: Type, jsw: anytype) !void {
784743 const active_tag = std.meta.activeTag(self);
785 var jsw = std.json.writeStream(w, 15);
786 jsw.whitespace = opts.whitespace;
787744 try jsw.beginArray();
788 try jsw.arrayElem();
789 try jsw.emitNumber(@intFromEnum(active_tag));
745 try jsw.write(@intFromEnum(active_tag));
790746 inline for (comptime std.meta.fields(Type)) |case| {
791747 if (@field(Type, case.name) == active_tag) {
792748 const current_value = @field(self, case.name);
793749 inline for (comptime std.meta.fields(case.type)) |f| {
794 try jsw.arrayElem();
795750 if (f.type == std.builtin.Type.Pointer.Size) {
796 try jsw.emitNumber(@intFromEnum(@field(current_value, f.name)));
751 try jsw.write(@intFromEnum(@field(current_value, f.name)));
797752 } else {
798 try std.json.stringify(@field(current_value, f.name), opts, w);
799 jsw.state_index -= 1;
753 try jsw.write(@field(current_value, f.name));
800754 }
801755 }
802756 }
......@@ -919,14 +873,8 @@ const DocData = struct {
919873 val: WalkResult,
920874 };
921875
922 pub fn jsonStringify(
923 self: Expr,
924 opts: std.json.StringifyOptions,
925 w: anytype,
926 ) @TypeOf(w).Error!void {
876 pub fn jsonStringify(self: Expr, jsw: anytype) !void {
927877 const active_tag = std.meta.activeTag(self);
928 var jsw = std.json.writeStream(w, 15);
929 jsw.whitespace = opts.whitespace;
930878 try jsw.beginObject();
931879 if (active_tag == .declIndex) {
932880 try jsw.objectField("declRef");
......@@ -935,14 +883,17 @@ const DocData = struct {
935883 }
936884 switch (self) {
937885 .int => {
938 if (self.int.negated) try w.writeAll("-");
939 try jsw.emitNumber(self.int.value);
886 if (self.int.negated) {
887 try jsw.write(-@as(i65, self.int.value));
888 } else {
889 try jsw.write(self.int.value);
890 }
940891 },
941892 .builtinField => {
942 try jsw.emitString(@tagName(self.builtinField));
893 try jsw.write(@tagName(self.builtinField));
943894 },
944895 .declRef => {
945 try jsw.emitNumber(self.declRef.Analyzed);
896 try jsw.write(self.declRef.Analyzed);
946897 },
947898 else => {
948899 inline for (comptime std.meta.fields(Expr)) |case| {
......@@ -952,14 +903,7 @@ const DocData = struct {
952903 if (comptime std.mem.eql(u8, case.name, "declRef"))
953904 continue;
954905 if (@field(Expr, case.name) == active_tag) {
955 try std.json.stringify(@field(self, case.name), opts, w);
956 jsw.state_index -= 1;
957 // TODO: we should not reach into the state of the
958 // json writer, but alas, this is what's
959 // necessary with the current api.
960 // would be nice to have a proper integration
961 // between the json writer and the generic
962 // std.json.stringify implementation
906 try jsw.write(@field(self, case.name));
963907 }
964908 }
965909 },
......@@ -5440,12 +5384,9 @@ fn writeFileTableToJson(
54405384 try jsw.beginArray();
54415385 var it = map.iterator();
54425386 while (it.next()) |entry| {
5443 try jsw.arrayElem();
54445387 try jsw.beginArray();
5445 try jsw.arrayElem();
5446 try jsw.emitString(entry.key_ptr.*.sub_file_path);
5447 try jsw.arrayElem();
5448 try jsw.emitNumber(mods.getIndex(entry.key_ptr.*.pkg) orelse 0);
5388 try jsw.write(entry.key_ptr.*.sub_file_path);
5389 try jsw.write(mods.getIndex(entry.key_ptr.*.pkg) orelse 0);
54495390 try jsw.endArray();
54505391 }
54515392 try jsw.endArray();
......@@ -5462,21 +5403,19 @@ fn writeGuidesToJson(sections: std.ArrayListUnmanaged(Section), jsw: anytype) !v
54625403
54635404 for (sections.items) |s| {
54645405 // section name
5465 try jsw.arrayElem();
54665406 try jsw.beginObject();
54675407 try jsw.objectField("name");
5468 try jsw.emitString(s.name);
5408 try jsw.write(s.name);
54695409 try jsw.objectField("guides");
54705410
54715411 // section value
54725412 try jsw.beginArray();
54735413 for (s.guides.items) |g| {
5474 try jsw.arrayElem();
54755414 try jsw.beginObject();
54765415 try jsw.objectField("name");
5477 try jsw.emitString(g.name);
5416 try jsw.write(g.name);
54785417 try jsw.objectField("body");
5479 try jsw.emitString(g.body);
5418 try jsw.write(g.body);
54805419 try jsw.endObject();
54815420 }
54825421 try jsw.endArray();
......@@ -5494,7 +5433,7 @@ fn writeModuleTableToJson(
54945433 var it = map.valueIterator();
54955434 while (it.next()) |entry| {
54965435 try jsw.objectField(entry.name);
5497 try jsw.emitNumber(entry.value);
5436 try jsw.write(entry.value);
54985437 }
54995438 try jsw.endObject();
55005439}
src/print_env.zig+8-7
......@@ -28,26 +28,27 @@ pub fn cmdEnv(gpa: Allocator, args: []const []const u8, stdout: std.fs.File.Writ
2828 var bw = std.io.bufferedWriter(stdout);
2929 const w = bw.writer();
3030
31 var jws = std.json.writeStream(w, 6);
31 var jws = std.json.writeStream(w, .{ .whitespace = .indent_1 });
32
3233 try jws.beginObject();
3334
3435 try jws.objectField("zig_exe");
35 try jws.emitString(self_exe_path);
36 try jws.write(self_exe_path);
3637
3738 try jws.objectField("lib_dir");
38 try jws.emitString(zig_lib_directory.path.?);
39 try jws.write(zig_lib_directory.path.?);
3940
4041 try jws.objectField("std_dir");
41 try jws.emitString(zig_std_dir);
42 try jws.write(zig_std_dir);
4243
4344 try jws.objectField("global_cache_dir");
44 try jws.emitString(global_cache_dir);
45 try jws.write(global_cache_dir);
4546
4647 try jws.objectField("version");
47 try jws.emitString(build_options.version);
48 try jws.write(build_options.version);
4849
4950 try jws.objectField("target");
50 try jws.emitString(triple);
51 try jws.write(triple);
5152
5253 try jws.endObject();
5354 try w.writeByte('\n');
src/print_targets.zig+14-23
......@@ -40,31 +40,28 @@ pub fn cmdTargets(
4040
4141 var bw = io.bufferedWriter(stdout);
4242 const w = bw.writer();
43 var jws = std.json.writeStream(w, 6);
43 var jws = std.json.writeStream(w, .{ .whitespace = .indent_1 });
4444
4545 try jws.beginObject();
4646
4747 try jws.objectField("arch");
4848 try jws.beginArray();
4949 for (meta.fieldNames(Target.Cpu.Arch)) |field| {
50 try jws.arrayElem();
51 try jws.emitString(field);
50 try jws.write(field);
5251 }
5352 try jws.endArray();
5453
5554 try jws.objectField("os");
5655 try jws.beginArray();
5756 for (meta.fieldNames(Target.Os.Tag)) |field| {
58 try jws.arrayElem();
59 try jws.emitString(field);
57 try jws.write(field);
6058 }
6159 try jws.endArray();
6260
6361 try jws.objectField("abi");
6462 try jws.beginArray();
6563 for (meta.fieldNames(Target.Abi)) |field| {
66 try jws.arrayElem();
67 try jws.emitString(field);
64 try jws.write(field);
6865 }
6966 try jws.endArray();
7067
......@@ -75,19 +72,16 @@ pub fn cmdTargets(
7572 @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi),
7673 });
7774 defer allocator.free(tmp);
78 try jws.arrayElem();
79 try jws.emitString(tmp);
75 try jws.write(tmp);
8076 }
8177 try jws.endArray();
8278
8379 try jws.objectField("glibc");
8480 try jws.beginArray();
8581 for (glibc_abi.all_versions) |ver| {
86 try jws.arrayElem();
87
8882 const tmp = try std.fmt.allocPrint(allocator, "{}", .{ver});
8983 defer allocator.free(tmp);
90 try jws.emitString(tmp);
84 try jws.write(tmp);
9185 }
9286 try jws.endArray();
9387
......@@ -102,8 +96,7 @@ pub fn cmdTargets(
10296 for (arch.allFeaturesList(), 0..) |feature, i_usize| {
10397 const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize));
10498 if (model.features.isEnabled(index)) {
105 try jws.arrayElem();
106 try jws.emitString(feature.name);
99 try jws.write(feature.name);
107100 }
108101 }
109102 try jws.endArray();
......@@ -118,8 +111,7 @@ pub fn cmdTargets(
118111 try jws.objectField(@tagName(arch));
119112 try jws.beginArray();
120113 for (arch.allFeaturesList()) |feature| {
121 try jws.arrayElem();
122 try jws.emitString(feature.name);
114 try jws.write(feature.name);
123115 }
124116 try jws.endArray();
125117 }
......@@ -131,17 +123,17 @@ pub fn cmdTargets(
131123 const triple = try native_target.zigTriple(allocator);
132124 defer allocator.free(triple);
133125 try jws.objectField("triple");
134 try jws.emitString(triple);
126 try jws.write(triple);
135127 }
136128 {
137129 try jws.objectField("cpu");
138130 try jws.beginObject();
139131 try jws.objectField("arch");
140 try jws.emitString(@tagName(native_target.cpu.arch));
132 try jws.write(@tagName(native_target.cpu.arch));
141133
142134 try jws.objectField("name");
143135 const cpu = native_target.cpu;
144 try jws.emitString(cpu.model.name);
136 try jws.write(cpu.model.name);
145137
146138 {
147139 try jws.objectField("features");
......@@ -149,8 +141,7 @@ pub fn cmdTargets(
149141 for (native_target.cpu.arch.allFeaturesList(), 0..) |feature, i_usize| {
150142 const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize));
151143 if (cpu.features.isEnabled(index)) {
152 try jws.arrayElem();
153 try jws.emitString(feature.name);
144 try jws.write(feature.name);
154145 }
155146 }
156147 try jws.endArray();
......@@ -158,9 +149,9 @@ pub fn cmdTargets(
158149 try jws.endObject();
159150 }
160151 try jws.objectField("os");
161 try jws.emitString(@tagName(native_target.os.tag));
152 try jws.write(@tagName(native_target.os.tag));
162153 try jws.objectField("abi");
163 try jws.emitString(@tagName(native_target.abi));
154 try jws.write(@tagName(native_target.abi));
164155 try jws.endObject();
165156
166157 try jws.endObject();