allow more complex comptime fields in std.json

This commit is contained in:
daurnimator
2021-02-01 00:55:22 +11:00
parent f88bb56ee5
commit e0a04e7f67

View File

@@ -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" {