Commit 1e087d3a64

Marcus Ramse <marcus@ramse.se>
2023-03-08 16:04:57
std.json: support tuples
1 parent cd3575b
Changed files (2)
lib
lib/std/json/test.zig
@@ -2459,6 +2459,56 @@ test "parse into struct ignoring unknown fields" {
     try testing.expectEqualSlices(u8, "zig", r.language);
 }
 
+test "parse into tuple" {
+    const options = ParseOptions{ .allocator = testing.allocator };
+    const Union = union(enum) {
+        char: u8,
+        float: f64,
+        string: []const u8,
+    };
+    const T = std.meta.Tuple(&.{
+        i64,
+        f64,
+        bool,
+        []const u8,
+        ?bool,
+        struct {
+            foo: i32,
+            bar: []const u8,
+        },
+        std.meta.Tuple(&.{ u8, []const u8, u8 }),
+        Union,
+    });
+    var ts = TokenStream.init(
+        \\[
+        \\  420,
+        \\  3.14,
+        \\  true,
+        \\  "zig",
+        \\  null,
+        \\  {
+        \\    "foo": 1,
+        \\    "bar": "zero"
+        \\  },
+        \\  [4, "två", 42],
+        \\  12.34
+        \\]
+    );
+    const r = try parse(T, &ts, options);
+    defer parseFree(T, r, options);
+    try testing.expectEqual(@as(i64, 420), r[0]);
+    try testing.expectEqual(@as(f64, 3.14), r[1]);
+    try testing.expectEqual(true, r[2]);
+    try testing.expectEqualSlices(u8, "zig", r[3]);
+    try testing.expectEqual(@as(?bool, null), r[4]);
+    try testing.expectEqual(@as(i32, 1), r[5].foo);
+    try testing.expectEqualSlices(u8, "zero", r[5].bar);
+    try testing.expectEqual(@as(u8, 4), r[6][0]);
+    try testing.expectEqualSlices(u8, "två", r[6][1]);
+    try testing.expectEqual(@as(u8, 42), r[6][2]);
+    try testing.expectEqual(Union{ .float = 12.34 }, r[7]);
+}
+
 const ParseIntoRecursiveUnionDefinitionValue = union(enum) {
     integer: i64,
     array: []const ParseIntoRecursiveUnionDefinitionValue,
lib/std/json.zig
@@ -1511,6 +1511,34 @@ fn parseInternal(
             }
         },
         .Struct => |structInfo| {
+            if (structInfo.is_tuple) {
+                switch (token) {
+                    .ArrayBegin => {},
+                    else => return error.UnexpectedToken,
+                }
+                var r: T = undefined;
+                var child_options = options;
+                child_options.allow_trailing_data = true;
+                var fields_seen: usize = 0;
+                errdefer {
+                    inline for (0..structInfo.fields.len) |i| {
+                        if (i < fields_seen) {
+                            parseFree(structInfo.fields[i].type, r[i], options);
+                        }
+                    }
+                }
+                inline for (0..structInfo.fields.len) |i| {
+                    r[i] = try parse(structInfo.fields[i].type, tokens, child_options);
+                    fields_seen = i + 1;
+                }
+                const tok = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
+                switch (tok) {
+                    .ArrayEnd => {},
+                    else => return error.UnexpectedToken,
+                }
+                return r;
+            }
+
             switch (token) {
                 .ObjectBegin => {},
                 else => return error.UnexpectedToken,
@@ -2290,7 +2318,7 @@ pub fn stringify(
                 return value.jsonStringify(options, out_stream);
             }
 
-            try out_stream.writeByte('{');
+            try out_stream.writeByte(if (S.is_tuple) '[' else '{');
             var field_output = false;
             var child_options = options;
             if (child_options.whitespace) |*child_whitespace| {
@@ -2320,11 +2348,13 @@ pub fn stringify(
                     if (child_options.whitespace) |child_whitespace| {
                         try child_whitespace.outputIndent(out_stream);
                     }
-                    try encodeJsonString(Field.name, options, out_stream);
-                    try out_stream.writeByte(':');
-                    if (child_options.whitespace) |child_whitespace| {
-                        if (child_whitespace.separator) {
-                            try out_stream.writeByte(' ');
+                    if (!S.is_tuple) {
+                        try encodeJsonString(Field.name, options, out_stream);
+                        try out_stream.writeByte(':');
+                        if (child_options.whitespace) |child_whitespace| {
+                            if (child_whitespace.separator) {
+                                try out_stream.writeByte(' ');
+                            }
                         }
                     }
                     try stringify(@field(value, Field.name), child_options, out_stream);
@@ -2335,7 +2365,7 @@ pub fn stringify(
                     try whitespace.outputIndent(out_stream);
                 }
             }
-            try out_stream.writeByte('}');
+            try out_stream.writeByte(if (S.is_tuple) ']' else '}');
             return;
         },
         .ErrorSet => return stringify(@as([]const u8, @errorName(value)), options, out_stream),
@@ -2649,6 +2679,10 @@ test "stringify vector" {
     try teststringify("[1,1]", @splat(2, @as(u32, 1)), StringifyOptions{});
 }
 
+test "stringify tuple" {
+    try teststringify("[\"foo\",42]", std.meta.Tuple(&.{ []const u8, usize }){ "foo", 42 }, StringifyOptions{});
+}
+
 fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void {
     const ValidationWriter = struct {
         const Self = @This();