Commit 339b628d4c

Mason Remaley <MasonRemaley@users.noreply.github.com>
2025-02-22 03:40:57
Output `zig targets` as ZON instead of JSON (#22939)
* Adds startTupleField/startStructField, makes pattern in print targets less verbose * Makes some enums into strings * Start/finish renamed to begin/end I feel bad changing this, but I don't know why I named them this way in the first place. Begin/end is consistent with the json API, and with other APIs in the wild that follow this pattern. Better to change now than later.
1 parent 65e7ede
Changed files (2)
lib/std/zon/stringify.zig
@@ -438,17 +438,17 @@ pub const SerializeContainerOptions = struct {
 /// * `multilineString`
 ///
 /// For manual serialization of containers, see:
-/// * `startStruct`
-/// * `startTuple`
+/// * `beginStruct`
+/// * `beginTuple`
 ///
 /// # Example
 /// ```zig
 /// var sz = serializer(writer, .{});
-/// var vec2 = try sz.startStruct(.{});
+/// var vec2 = try sz.beginStruct(.{});
 /// try vec2.field("x", 1.5, .{});
 /// try vec2.fieldPrefix();
 /// try sz.value(2.5);
-/// try vec2.finish();
+/// try vec2.end();
 /// ```
 pub fn Serializer(Writer: type) type {
     return struct {
@@ -525,22 +525,22 @@ pub fn Serializer(Writer: type) type {
                     }
                 },
                 .array => {
-                    var container = try self.startTuple(
+                    var container = try self.beginTuple(
                         .{ .whitespace_style = .{ .fields = val.len } },
                     );
                     for (val) |item_val| {
                         try container.fieldArbitraryDepth(item_val, options);
                     }
-                    try container.finish();
+                    try container.end();
                 },
                 .@"struct" => |@"struct"| if (@"struct".is_tuple) {
-                    var container = try self.startTuple(
+                    var container = try self.beginTuple(
                         .{ .whitespace_style = .{ .fields = @"struct".fields.len } },
                     );
                     inline for (val) |field_value| {
                         try container.fieldArbitraryDepth(field_value, options);
                     }
-                    try container.finish();
+                    try container.end();
                 } else {
                     // Decide which fields to emit
                     const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: {
@@ -562,7 +562,7 @@ pub fn Serializer(Writer: type) type {
                     };
 
                     // Emit those fields
-                    var container = try self.startStruct(
+                    var container = try self.beginStruct(
                         .{ .whitespace_style = .{ .fields = fields } },
                     );
                     inline for (@"struct".fields, skipped) |field_info, skip| {
@@ -574,7 +574,7 @@ pub fn Serializer(Writer: type) type {
                             );
                         }
                     }
-                    try container.finish();
+                    try container.end();
                 },
                 .@"union" => |@"union"| {
                     comptime assert(@"union".tag_type != null);
@@ -582,7 +582,7 @@ pub fn Serializer(Writer: type) type {
                         inline else => |pl, tag| if (@TypeOf(pl) == void)
                             try self.writer.print(".{s}", .{@tagName(tag)})
                         else {
-                            var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } });
+                            var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
 
                             try container.fieldArbitraryDepth(
                                 @tagName(tag),
@@ -590,7 +590,7 @@ pub fn Serializer(Writer: type) type {
                                 options,
                             );
 
-                            try container.finish();
+                            try container.end();
                         },
                     }
                 },
@@ -600,13 +600,13 @@ pub fn Serializer(Writer: type) type {
                     try self.writer.writeAll("null");
                 },
                 .vector => |vector| {
-                    var container = try self.startTuple(
+                    var container = try self.beginTuple(
                         .{ .whitespace_style = .{ .fields = vector.len } },
                     );
                     for (0..vector.len) |i| {
                         try container.fieldArbitraryDepth(val[i], options);
                     }
-                    try container.finish();
+                    try container.end();
                 },
 
                 else => comptime unreachable,
@@ -691,18 +691,18 @@ pub fn Serializer(Writer: type) type {
             comptime assert(canSerializeType(@TypeOf(val)));
             switch (@typeInfo(@TypeOf(val))) {
                 .@"struct" => {
-                    var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } });
+                    var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
                     inline for (val) |item_val| {
                         try container.fieldArbitraryDepth(item_val, options);
                     }
-                    try container.finish();
+                    try container.end();
                 },
                 .pointer, .array => {
-                    var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } });
+                    var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
                     for (val) |item_val| {
                         try container.fieldArbitraryDepth(item_val, options);
                     }
-                    try container.finish();
+                    try container.end();
                 },
                 else => comptime unreachable,
             }
@@ -767,19 +767,19 @@ pub fn Serializer(Writer: type) type {
         }
 
         /// Create a `Struct` for writing ZON structs field by field.
-        pub fn startStruct(
+        pub fn beginStruct(
             self: *Self,
             options: SerializeContainerOptions,
         ) Writer.Error!Struct {
-            return Struct.start(self, options);
+            return Struct.begin(self, options);
         }
 
         /// Creates a `Tuple` for writing ZON tuples field by field.
-        pub fn startTuple(
+        pub fn beginTuple(
             self: *Self,
             options: SerializeContainerOptions,
         ) Writer.Error!Tuple {
-            return Tuple.start(self, options);
+            return Tuple.begin(self, options);
         }
 
         fn indent(self: *Self) Writer.Error!void {
@@ -812,17 +812,17 @@ pub fn Serializer(Writer: type) type {
         pub const Tuple = struct {
             container: Container,
 
-            fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Tuple {
+            fn begin(parent: *Self, options: SerializeContainerOptions) Writer.Error!Tuple {
                 return .{
-                    .container = try Container.start(parent, .anon, options),
+                    .container = try Container.begin(parent, .anon, options),
                 };
             }
 
             /// Finishes serializing the tuple.
             ///
             /// Prints a trailing comma as configured when appropriate, and the closing bracket.
-            pub fn finish(self: *Tuple) Writer.Error!void {
-                try self.container.finish();
+            pub fn end(self: *Tuple) Writer.Error!void {
+                try self.container.end();
                 self.* = undefined;
             }
 
@@ -855,6 +855,24 @@ pub fn Serializer(Writer: type) type {
                 try self.container.fieldArbitraryDepth(null, val, options);
             }
 
+            /// Starts a field with a struct as a value. Returns the struct.
+            pub fn beginStructField(
+                self: *Tuple,
+                options: SerializeContainerOptions,
+            ) Writer.Error!Struct {
+                try self.fieldPrefix();
+                return self.container.serializer.beginStruct(options);
+            }
+
+            /// Starts a field with a tuple as a value. Returns the tuple.
+            pub fn beginTupleField(
+                self: *Tuple,
+                options: SerializeContainerOptions,
+            ) Writer.Error!Tuple {
+                try self.fieldPrefix();
+                return self.container.serializer.beginTuple(options);
+            }
+
             /// Print a field prefix. This prints any necessary commas, and whitespace as
             /// configured. Useful if you want to serialize the field value yourself.
             pub fn fieldPrefix(self: *Tuple) Writer.Error!void {
@@ -866,17 +884,17 @@ pub fn Serializer(Writer: type) type {
         pub const Struct = struct {
             container: Container,
 
-            fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Struct {
+            fn begin(parent: *Self, options: SerializeContainerOptions) Writer.Error!Struct {
                 return .{
-                    .container = try Container.start(parent, .named, options),
+                    .container = try Container.begin(parent, .named, options),
                 };
             }
 
             /// Finishes serializing the struct.
             ///
             /// Prints a trailing comma as configured when appropriate, and the closing bracket.
-            pub fn finish(self: *Struct) Writer.Error!void {
-                try self.container.finish();
+            pub fn end(self: *Struct) Writer.Error!void {
+                try self.container.end();
                 self.* = undefined;
             }
 
@@ -912,6 +930,26 @@ pub fn Serializer(Writer: type) type {
                 try self.container.fieldArbitraryDepth(name, val, options);
             }
 
+            /// Starts a field with a struct as a value. Returns the struct.
+            pub fn beginStructField(
+                self: *Struct,
+                name: []const u8,
+                options: SerializeContainerOptions,
+            ) Writer.Error!Struct {
+                try self.fieldPrefix(name);
+                return self.container.serializer.beginStruct(options);
+            }
+
+            /// Starts a field with a tuple as a value. Returns the tuple.
+            pub fn beginTupleField(
+                self: *Struct,
+                name: []const u8,
+                options: SerializeContainerOptions,
+            ) Writer.Error!Tuple {
+                try self.fieldPrefix(name);
+                return self.container.serializer.beginTuple(options);
+            }
+
             /// Print a field prefix. This prints any necessary commas, the field name (escaped if
             /// necessary) and whitespace as configured. Useful if you want to serialize the field
             /// value yourself.
@@ -928,7 +966,7 @@ pub fn Serializer(Writer: type) type {
             options: SerializeContainerOptions,
             empty: bool,
 
-            fn start(
+            fn begin(
                 sz: *Self,
                 field_style: FieldStyle,
                 options: SerializeContainerOptions,
@@ -943,7 +981,7 @@ pub fn Serializer(Writer: type) type {
                 };
             }
 
-            fn finish(self: *Container) Writer.Error!void {
+            fn end(self: *Container) Writer.Error!void {
                 if (self.options.shouldWrap()) self.serializer.indent_level -|= 1;
                 if (!self.empty) {
                     if (self.options.shouldWrap()) {
@@ -1139,52 +1177,52 @@ test "std.zon stringify whitespace, low level API" {
 
         // Empty containers
         {
-            var container = try sz.startStruct(.{});
-            try container.finish();
+            var container = try sz.beginStruct(.{});
+            try container.end();
             try std.testing.expectEqualStrings(".{}", buf.items);
             buf.clearRetainingCapacity();
         }
 
         {
-            var container = try sz.startTuple(.{});
-            try container.finish();
+            var container = try sz.beginTuple(.{});
+            try container.end();
             try std.testing.expectEqualStrings(".{}", buf.items);
             buf.clearRetainingCapacity();
         }
 
         {
-            var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } });
-            try container.finish();
+            var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
+            try container.end();
             try std.testing.expectEqualStrings(".{}", buf.items);
             buf.clearRetainingCapacity();
         }
 
         {
-            var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } });
-            try container.finish();
+            var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
+            try container.end();
             try std.testing.expectEqualStrings(".{}", buf.items);
             buf.clearRetainingCapacity();
         }
 
         {
-            var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 0 } });
-            try container.finish();
+            var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 0 } });
+            try container.end();
             try std.testing.expectEqualStrings(".{}", buf.items);
             buf.clearRetainingCapacity();
         }
 
         {
-            var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 0 } });
-            try container.finish();
+            var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 0 } });
+            try container.end();
             try std.testing.expectEqualStrings(".{}", buf.items);
             buf.clearRetainingCapacity();
         }
 
         // Size 1
         {
-            var container = try sz.startStruct(.{});
+            var container = try sz.beginStruct(.{});
             try container.field("a", 1, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(
                     \\.{
@@ -1198,9 +1236,9 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startTuple(.{});
+            var container = try sz.beginTuple(.{});
             try container.field(1, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(
                     \\.{
@@ -1214,9 +1252,9 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } });
+            var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
             try container.field("a", 1, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items);
             } else {
@@ -1228,9 +1266,9 @@ test "std.zon stringify whitespace, low level API" {
         {
             // We get extra spaces here, since we didn't know up front that there would only be one
             // field.
-            var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } });
+            var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
             try container.field(1, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(".{ 1 }", buf.items);
             } else {
@@ -1240,9 +1278,9 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 1 } });
+            var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
             try container.field("a", 1, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items);
             } else {
@@ -1252,19 +1290,19 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 1 } });
+            var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 1 } });
             try container.field(1, .{});
-            try container.finish();
+            try container.end();
             try std.testing.expectEqualStrings(".{1}", buf.items);
             buf.clearRetainingCapacity();
         }
 
         // Size 2
         {
-            var container = try sz.startStruct(.{});
+            var container = try sz.beginStruct(.{});
             try container.field("a", 1, .{});
             try container.field("b", 2, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(
                     \\.{
@@ -1279,10 +1317,10 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startTuple(.{});
+            var container = try sz.beginTuple(.{});
             try container.field(1, .{});
             try container.field(2, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(
                     \\.{
@@ -1297,10 +1335,10 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } });
+            var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
             try container.field("a", 1, .{});
             try container.field("b", 2, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items);
             } else {
@@ -1310,10 +1348,10 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } });
+            var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
             try container.field(1, .{});
             try container.field(2, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
             } else {
@@ -1323,10 +1361,10 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 2 } });
+            var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 2 } });
             try container.field("a", 1, .{});
             try container.field("b", 2, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items);
             } else {
@@ -1336,10 +1374,10 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 2 } });
+            var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 2 } });
             try container.field(1, .{});
             try container.field(2, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
             } else {
@@ -1350,11 +1388,11 @@ test "std.zon stringify whitespace, low level API" {
 
         // Size 3
         {
-            var container = try sz.startStruct(.{});
+            var container = try sz.beginStruct(.{});
             try container.field("a", 1, .{});
             try container.field("b", 2, .{});
             try container.field("c", 3, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(
                     \\.{
@@ -1370,11 +1408,11 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startTuple(.{});
+            var container = try sz.beginTuple(.{});
             try container.field(1, .{});
             try container.field(2, .{});
             try container.field(3, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(
                     \\.{
@@ -1390,11 +1428,11 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } });
+            var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
             try container.field("a", 1, .{});
             try container.field("b", 2, .{});
             try container.field("c", 3, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2, .c = 3 }", buf.items);
             } else {
@@ -1404,11 +1442,11 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } });
+            var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
             try container.field(1, .{});
             try container.field(2, .{});
             try container.field(3, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(".{ 1, 2, 3 }", buf.items);
             } else {
@@ -1418,11 +1456,11 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 3 } });
+            var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 3 } });
             try container.field("a", 1, .{});
             try container.field("b", 2, .{});
             try container.field("c", 3, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(
                     \\.{
@@ -1438,11 +1476,11 @@ test "std.zon stringify whitespace, low level API" {
         }
 
         {
-            var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 3 } });
+            var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 3 } });
             try container.field(1, .{});
             try container.field(2, .{});
             try container.field(3, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(
                     \\.{
@@ -1459,10 +1497,10 @@ test "std.zon stringify whitespace, low level API" {
 
         // Nested objects where the outer container doesn't wrap but the inner containers do
         {
-            var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } });
+            var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
             try container.field("first", .{ 1, 2, 3 }, .{});
             try container.field("second", .{ 4, 5, 6 }, .{});
-            try container.finish();
+            try container.end();
             if (whitespace) {
                 try std.testing.expectEqualStrings(
                     \\.{ .first = .{
@@ -2016,26 +2054,26 @@ test "std.zon depth limits" {
         try sz.value(3, .{});
         try sz.valueArbitraryDepth(maybe_recurse, .{});
 
-        var s = try sz.startStruct(.{});
+        var s = try sz.beginStruct(.{});
         try std.testing.expectError(error.ExceededMaxDepth, s.fieldMaxDepth("a", 1, .{}, 0));
         try s.fieldMaxDepth("b", 4, .{}, 1);
         try s.field("c", 5, .{});
         try s.fieldArbitraryDepth("d", maybe_recurse, .{});
-        try s.finish();
+        try s.end();
 
-        var t = try sz.startTuple(.{});
+        var t = try sz.beginTuple(.{});
         try std.testing.expectError(error.ExceededMaxDepth, t.fieldMaxDepth(1, .{}, 0));
         try t.fieldMaxDepth(6, .{}, 1);
         try t.field(7, .{});
         try t.fieldArbitraryDepth(maybe_recurse, .{});
-        try t.finish();
+        try t.end();
 
-        var a = try sz.startTuple(.{});
+        var a = try sz.beginTuple(.{});
         try std.testing.expectError(error.ExceededMaxDepth, a.fieldMaxDepth(1, .{}, 0));
         try a.fieldMaxDepth(8, .{}, 1);
         try a.field(9, .{});
         try a.fieldArbitraryDepth(maybe_recurse, .{});
-        try a.finish();
+        try a.end();
 
         try std.testing.expectEqualStrings(
             \\23.{}.{
@@ -2315,3 +2353,73 @@ test "std.zon pointers" {
         , val, .{});
     }
 }
+
+test "std.zon tuple/struct field" {
+    var buf = std.ArrayList(u8).init(std.testing.allocator);
+    defer buf.deinit();
+    var sz = serializer(buf.writer(), .{});
+
+    // Test on structs
+    {
+        var root = try sz.beginStruct(.{});
+        {
+            var tuple = try root.beginTupleField("foo", .{});
+            try tuple.field(0, .{});
+            try tuple.field(1, .{});
+            try tuple.end();
+        }
+        {
+            var strct = try root.beginStructField("bar", .{});
+            try strct.field("a", 0, .{});
+            try strct.field("b", 1, .{});
+            try strct.end();
+        }
+        try root.end();
+
+        try std.testing.expectEqualStrings(
+            \\.{
+            \\    .foo = .{
+            \\        0,
+            \\        1,
+            \\    },
+            \\    .bar = .{
+            \\        .a = 0,
+            \\        .b = 1,
+            \\    },
+            \\}
+        , buf.items);
+        buf.clearRetainingCapacity();
+    }
+
+    // Test on tuples
+    {
+        var root = try sz.beginTuple(.{});
+        {
+            var tuple = try root.beginTupleField(.{});
+            try tuple.field(0, .{});
+            try tuple.field(1, .{});
+            try tuple.end();
+        }
+        {
+            var strct = try root.beginStructField(.{});
+            try strct.field("a", 0, .{});
+            try strct.field("b", 1, .{});
+            try strct.end();
+        }
+        try root.end();
+
+        try std.testing.expectEqualStrings(
+            \\.{
+            \\    .{
+            \\        0,
+            \\        1,
+            \\    },
+            \\    .{
+            \\        .a = 0,
+            \\        .b = 1,
+            \\    },
+            \\}
+        , buf.items);
+        buf.clearRetainingCapacity();
+    }
+}
src/print_targets.zig
@@ -40,121 +40,101 @@ pub fn cmdTargets(
 
     var bw = io.bufferedWriter(stdout);
     const w = bw.writer();
-    var jws = std.json.writeStream(w, .{ .whitespace = .indent_1 });
+    var sz = std.zon.stringify.serializer(w, .{});
 
-    try jws.beginObject();
-
-    try jws.objectField("arch");
-    try jws.beginArray();
-    for (meta.fieldNames(Target.Cpu.Arch)) |field| {
-        try jws.write(field);
-    }
-    try jws.endArray();
+    {
+        var root_obj = try sz.beginStruct(.{});
 
-    try jws.objectField("os");
-    try jws.beginArray();
-    for (meta.fieldNames(Target.Os.Tag)) |field| {
-        try jws.write(field);
-    }
-    try jws.endArray();
+        try root_obj.field("arch", meta.fieldNames(Target.Cpu.Arch), .{});
+        try root_obj.field("os", meta.fieldNames(Target.Os.Tag), .{});
+        try root_obj.field("abi", meta.fieldNames(Target.Abi), .{});
 
-    try jws.objectField("abi");
-    try jws.beginArray();
-    for (meta.fieldNames(Target.Abi)) |field| {
-        try jws.write(field);
-    }
-    try jws.endArray();
-
-    try jws.objectField("libc");
-    try jws.beginArray();
-    for (std.zig.target.available_libcs) |libc| {
-        const tmp = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{
-            @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi),
-        });
-        defer allocator.free(tmp);
-        try jws.write(tmp);
-    }
-    try jws.endArray();
-
-    try jws.objectField("glibc");
-    try jws.beginArray();
-    for (glibc_abi.all_versions) |ver| {
-        const tmp = try std.fmt.allocPrint(allocator, "{}", .{ver});
-        defer allocator.free(tmp);
-        try jws.write(tmp);
-    }
-    try jws.endArray();
-
-    try jws.objectField("cpus");
-    try jws.beginObject();
-    for (meta.tags(Target.Cpu.Arch)) |arch| {
-        try jws.objectField(@tagName(arch));
-        try jws.beginObject();
-        for (arch.allCpuModels()) |model| {
-            try jws.objectField(model.name);
-            try jws.beginArray();
-            for (arch.allFeaturesList(), 0..) |feature, i_usize| {
-                const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize));
-                if (model.features.isEnabled(index)) {
-                    try jws.write(feature.name);
-                }
+        {
+            var libc_obj = try root_obj.beginTupleField("libc", .{});
+            for (std.zig.target.available_libcs) |libc| {
+                const tmp = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{
+                    @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi),
+                });
+                defer allocator.free(tmp);
+                try libc_obj.field(tmp, .{});
             }
-            try jws.endArray();
+            try libc_obj.end();
         }
-        try jws.endObject();
-    }
-    try jws.endObject();
-
-    try jws.objectField("cpuFeatures");
-    try jws.beginObject();
-    for (meta.tags(Target.Cpu.Arch)) |arch| {
-        try jws.objectField(@tagName(arch));
-        try jws.beginArray();
-        for (arch.allFeaturesList()) |feature| {
-            try jws.write(feature.name);
+
+        {
+            var glibc_obj = try root_obj.beginTupleField("glibc", .{});
+            for (glibc_abi.all_versions) |ver| {
+                const tmp = try std.fmt.allocPrint(allocator, "{}", .{ver});
+                defer allocator.free(tmp);
+                try glibc_obj.field(tmp, .{});
+            }
+            try glibc_obj.end();
         }
-        try jws.endArray();
-    }
-    try jws.endObject();
 
-    try jws.objectField("native");
-    try jws.beginObject();
-    {
-        const triple = try native_target.zigTriple(allocator);
-        defer allocator.free(triple);
-        try jws.objectField("triple");
-        try jws.write(triple);
-    }
-    {
-        try jws.objectField("cpu");
-        try jws.beginObject();
-        try jws.objectField("arch");
-        try jws.write(@tagName(native_target.cpu.arch));
+        {
+            var cpus_obj = try root_obj.beginStructField("cpus", .{});
+            for (meta.tags(Target.Cpu.Arch)) |arch| {
+                var arch_obj = try cpus_obj.beginStructField(@tagName(arch), .{});
+                for (arch.allCpuModels()) |model| {
+                    var features = try arch_obj.beginTupleField(model.name, .{});
+                    for (arch.allFeaturesList(), 0..) |feature, i_usize| {
+                        const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize));
+                        if (model.features.isEnabled(index)) {
+                            try features.field(feature.name, .{});
+                        }
+                    }
+                    try features.end();
+                }
+                try arch_obj.end();
+            }
+            try cpus_obj.end();
+        }
 
-        try jws.objectField("name");
-        const cpu = native_target.cpu;
-        try jws.write(cpu.model.name);
+        {
+            var cpu_features_obj = try root_obj.beginStructField("cpu_features", .{});
+            for (meta.tags(Target.Cpu.Arch)) |arch| {
+                var arch_features = try cpu_features_obj.beginTupleField(@tagName(arch), .{});
+                for (arch.allFeaturesList()) |feature| {
+                    try arch_features.field(feature.name, .{});
+                }
+                try arch_features.end();
+            }
+            try cpu_features_obj.end();
+        }
 
         {
-            try jws.objectField("features");
-            try jws.beginArray();
-            for (native_target.cpu.arch.allFeaturesList(), 0..) |feature, i_usize| {
-                const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize));
-                if (cpu.features.isEnabled(index)) {
-                    try jws.write(feature.name);
+            var native_obj = try root_obj.beginStructField("native", .{});
+            {
+                const triple = try native_target.zigTriple(allocator);
+                defer allocator.free(triple);
+                try native_obj.field("triple", triple, .{});
+            }
+            {
+                var cpu_obj = try native_obj.beginStructField("cpu", .{});
+                try cpu_obj.field("arch", @tagName(native_target.cpu.arch), .{});
+
+                try cpu_obj.field("name", native_target.cpu.model.name, .{});
+
+                {
+                    var features = try native_obj.beginTupleField("features", .{});
+                    for (native_target.cpu.arch.allFeaturesList(), 0..) |feature, i_usize| {
+                        const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize));
+                        if (native_target.cpu.features.isEnabled(index)) {
+                            try features.field(feature.name, .{});
+                        }
+                    }
+                    try features.end();
                 }
+                try cpu_obj.end();
             }
-            try jws.endArray();
+
+            try native_obj.field("os", @tagName(native_target.os.tag), .{});
+            try native_obj.field("abi", @tagName(native_target.abi), .{});
+            try native_obj.end();
         }
-        try jws.endObject();
-    }
-    try jws.objectField("os");
-    try jws.write(@tagName(native_target.os.tag));
-    try jws.objectField("abi");
-    try jws.write(@tagName(native_target.abi));
-    try jws.endObject();
 
-    try jws.endObject();
+        try root_obj.end();
+    }
 
     try w.writeByte('\n');
     return bw.flush();