From e0a04e7f6739aa0d81e3102c04af37ab51e550d4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 1 Feb 2021 00:55:22 +1100 Subject: [PATCH] allow more complex comptime fields in std.json --- lib/std/json.zig | 107 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 13 deletions(-) diff --git a/lib/std/json.zig b/lib/std/json.zig index 5e13233d17..6a4fb827f9 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -1374,6 +1374,65 @@ test "Value.jsonStringify" { } } +/// parse tokens from a stream, returning `false` if they do not decode to `value` +fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool { + // TODO: should be able to write this function to not require an allocator + const tmp = try parse(T, tokens, options); + defer parseFree(T, tmp, options); + + return parsedEqual(tmp, value); +} + +/// Returns if a value returned by `parse` is deep-equal to another value +fn parsedEqual(a: anytype, b: @TypeOf(a)) bool { + switch (@typeInfo(@TypeOf(a))) { + .Optional => { + if (a == null and b == null) return true; + if (a == null or b == null) return false; + return parsedEqual(a.?, b.?); + }, + .Union => |unionInfo| { + if (info.tag_type) |UnionTag| { + const tag_a = std.meta.activeTag(a); + const tag_b = std.meta.activeTag(b); + if (tag_a != tag_b) return false; + + inline for (info.fields) |field_info| { + if (@field(UnionTag, field_info.name) == tag_a) { + return parsedEqual(@field(a, field_info.name), @field(b, field_info.name)); + } + } + return false; + } else { + unreachable; + } + }, + .Array => { + for (a) |e, i| + if (!parsedEqual(e, b[i])) return false; + return true; + }, + .Struct => |info| { + inline for (info.fields) |field_info| { + if (!parsedEqual(@field(a, field_info.name), @field(b, field_info.name))) return false; + } + return true; + }, + .Pointer => |ptrInfo| switch (ptrInfo.size) { + .One => return parsedEqual(a.*, b.*), + .Slice => { + if (a.len != b.len) return false; + for (a) |e, i| + if (!parsedEqual(e, b[i])) return false; + return true; + }, + .Many, .C => unreachable, + }, + else => return a == b, + } + unreachable; +} + pub const ParseOptions = struct { allocator: ?*Allocator = null, @@ -1507,9 +1566,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: } } if (field.is_comptime) { - const value = try parse(field.field_type, tokens, options); - defer parseFree(field.field_type, value, options); - if (value != @field(r, field.name)) { + if (!try parsesTo(field.field_type, field.default_value.?, tokens, options)) { return error.UnexpectedValue; } } else { @@ -1809,16 +1866,40 @@ test "parseFree descends into tagged union" { } test "parse with comptime field" { - const T = struct { - comptime a: i32 = 0, - b: bool, - }; - testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &TokenStream.init( - \\{ - \\ "a": 0, - \\ "b": true - \\} - ), ParseOptions{})); + { + const T = struct { + comptime a: i32 = 0, + b: bool, + }; + testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &TokenStream.init( + \\{ + \\ "a": 0, + \\ "b": true + \\} + ), ParseOptions{})); + } + + { // string comptime values currently require an allocator + const T = union(enum) { + foo: struct { + comptime kind: []const u8 = "boolean", + b: bool, + }, + bar: struct { + comptime kind: []const u8 = "float", + b: f64, + }, + }; + + const r = try std.json.parse(T, &std.json.TokenStream.init( + \\{ + \\ "kind": "float", + \\ "b": 1.0 + \\} + ), .{ + .allocator = std.testing.allocator, + }); + } } test "parse into struct with no fields" {