Ok, I have a working solution now, thank you all for your help!
The functions look like this:
fn makeJsonParse(comptime T: type) fn (std.mem.Allocator, *std.json.Scanner, std.json.ParseOptions) std.json.ParseError(std.json.Scanner)!T {
return struct {
fn jsonParse(allocator: std.mem.Allocator, source: *std.json.Scanner, options: std.json.ParseOptions) std.json.ParseError(std.json.Scanner)!T {
const parsed = try std.json.innerParse(std.json.Value, allocator, source, options);
if (parsed != .object) {
return error.UnexpectedToken;
}
return T.jsonParseFromValue(allocator, parsed, options);
}
}.jsonParse;
}
fn makeJsonParseFromValue(comptime T: type, comptime discriminator: []const u8) fn (std.mem.Allocator, std.json.Value, std.json.ParseOptions) std.json.ParseFromValueError!T {
switch (@typeInfo(T)) {
.Union => {},
else => @compileError("Type must be a union"),
}
return struct {
pub fn jsonParseFromValue(allocator: std.mem.Allocator, source: std.json.Value, options: std.json.ParseOptions) std.json.ParseFromValueError!T {
if (source.object.get(discriminator)) |t| {
var opts = options;
opts.ignore_unknown_fields = true;
inline for (@typeInfo(T).Union.fields) |u_field| {
if (std.mem.eql(u8, t.string, u_field.name)) {
const p = try std.json.parseFromValue(u_field.type, allocator, source, opts);
const ret = @unionInit(T, u_field.name, p.value);
return ret;
}
}
}
return error.MissingField;
}
}.jsonParseFromValue;
}
// Example usage
const Field = union(enum) {
boolean: Boolean,
integer: Integer,
pub const jsonParse = makeJsonParse(@This());
pub const jsonParseFromValue = makeJsonParseFromValue(@This(), "type");
};
const Boolean = struct {
pub const @"type": []const u8 = "boolean";
description: ?[]const u8 = null,
default: ?bool = null,
@"const": ?bool = null,
};
const Integer = struct {
pub const @"type": []const u8 = "integer";
description: ?[]const u8 = null,
minimum: ?i64 = null,
maximum: ?i64 = null,
@"enum": ?[]i64 = null,
default: ?i64 = null,
@"const": ?i64 = null,
};
Which is more or less a generic version of @Travis’s solution. I learned a lot about how Zig works!
I think possible improvements would be to add a compile time check to make sure the union field types actually have the field as a declaration but I’ll leave that for later. If anybody has any other suggestions please tell me.