2024-02-28 15:47:37 +00:00
|
|
|
pub const std = @import("std");
|
2024-10-28 11:21:46 +00:00
|
|
|
const ParseFromValueError = std.json.ParseFromValueError;
|
2024-02-28 15:47:37 +00:00
|
|
|
|
2024-10-28 11:21:46 +00:00
|
|
|
/// Handle json fields that can have different Zig types depending on the message.
|
|
|
|
|
/// Each union field should have a unique Zig type.
|
|
|
|
|
///
|
|
|
|
|
/// Example json:
|
|
|
|
|
///
|
|
|
|
|
/// ```json
|
|
|
|
|
/// [
|
|
|
|
|
/// { "question": "How old are you ?", "answer": 5 },
|
|
|
|
|
/// { "question": "Count to three.", "answer": [1, 2, 3] },
|
|
|
|
|
/// ]
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// Corresponding Zig code:
|
|
|
|
|
///
|
|
|
|
|
/// ```zig
|
|
|
|
|
/// const Answer = union {
|
|
|
|
|
/// number: i32,
|
|
|
|
|
/// numbers: []const i32,
|
|
|
|
|
/// };
|
|
|
|
|
///
|
|
|
|
|
/// const Message = struct {
|
|
|
|
|
/// question: []const u8;
|
|
|
|
|
/// answer: stdx.json.Union(Answer);
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
2024-02-28 15:47:37 +00:00
|
|
|
pub fn Union(comptime T: type) type {
|
|
|
|
|
return struct {
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
|
|
value: T,
|
|
|
|
|
|
2024-10-28 11:21:46 +00:00
|
|
|
pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) std.json.ParseError(@TypeOf(source.*))!Self {
|
2024-02-28 15:47:37 +00:00
|
|
|
return jsonParseFromValue(
|
|
|
|
|
allocator,
|
2024-10-28 11:21:46 +00:00
|
|
|
try std.json.innerParse(std.json.Value, allocator, source, options),
|
2024-02-28 15:47:37 +00:00
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-28 11:21:46 +00:00
|
|
|
pub fn jsonParseFromValue(allocator: std.mem.Allocator, source: std.json.Value, options: std.json.ParseOptions) ParseFromValueError!Self {
|
2024-02-28 15:47:37 +00:00
|
|
|
inline for (std.meta.fields(T)) |field| {
|
|
|
|
|
switch (field.type) {
|
|
|
|
|
bool => if (source == .bool) return .{ .value = @unionInit(T, field.name, source.bool) },
|
|
|
|
|
[]const u8 => switch (source) {
|
|
|
|
|
.string => |v| return .{ .value = @unionInit(T, field.name, v) },
|
|
|
|
|
.number_string => |v| return .{ .value = @unionInit(T, field.name, v) },
|
|
|
|
|
else => {},
|
|
|
|
|
},
|
|
|
|
|
else => switch (@typeInfo(field.type)) {
|
2024-07-02 14:19:04 +00:00
|
|
|
.int => if (source == .integer) return .{ .value = @unionInit(T, field.name, @intCast(source.integer)) },
|
|
|
|
|
.float => if (source == .float) return .{ .value = @unionInit(T, field.name, @floatCast(source.float)) },
|
|
|
|
|
.@"struct" => if (source == .object) return .{ .value = @unionInit(T, field.name, try std.json.innerParseFromValue(field.type, allocator, source.object, options)) },
|
2024-02-28 15:47:37 +00:00
|
|
|
inline else => switch (source) {
|
|
|
|
|
.number_string, .array => return .{ .value = @unionInit(T, field.name, try std.json.innerParseFromValue(field.type, allocator, source, options)) },
|
|
|
|
|
else => {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-28 11:21:46 +00:00
|
|
|
return error.UnknownField;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Handle json fields that can have different Zig types depending on another field in the same message.
|
|
|
|
|
/// This is translated to a Zig tagged union.
|
|
|
|
|
///
|
|
|
|
|
/// Example json:
|
|
|
|
|
///
|
|
|
|
|
/// ```json
|
|
|
|
|
/// [
|
|
|
|
|
/// { "type": "faq", "question": "How old are you ?", "answer": 5 },
|
|
|
|
|
/// { "type": "address", "city": "NYC", "zipcode": "49130"},
|
|
|
|
|
/// ]
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// Corresponding Zig struct:
|
|
|
|
|
///
|
|
|
|
|
/// ```zig
|
|
|
|
|
/// const Entry = union {
|
|
|
|
|
/// faq: struct { question: []const u8, answer: u32 },
|
|
|
|
|
/// address: struct { city: []const u8, zipcode: []const u8 },
|
|
|
|
|
/// };
|
|
|
|
|
///
|
|
|
|
|
/// const Message = []const stdx.json.TaggedUnion(Entry, "type");
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn TaggedUnion(comptime T: type, comptime tag_name: [:0]const u8) type {
|
|
|
|
|
return struct {
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
|
|
value: T,
|
|
|
|
|
|
|
|
|
|
pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) std.json.ParseError(@TypeOf(source.*))!Self {
|
|
|
|
|
return jsonParseFromValue(
|
|
|
|
|
allocator,
|
|
|
|
|
try std.json.innerParse(std.json.Value, allocator, source, options),
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn jsonParseFromValue(allocator: std.mem.Allocator, source: std.json.Value, options: std.json.ParseOptions) ParseFromValueError!Self {
|
|
|
|
|
errdefer std.log.warn("failed to parse: {} as {s}", .{ source, @typeName(T) });
|
|
|
|
|
if (source != .object) return error.UnexpectedToken;
|
|
|
|
|
const o = source.object;
|
|
|
|
|
const tag = o.get(tag_name) orelse return error.MissingField;
|
|
|
|
|
for (o.keys(), o.values()) |k, v| {
|
|
|
|
|
std.log.warn("object['{s}'] = {}", .{ k, v });
|
|
|
|
|
}
|
|
|
|
|
if (tag != .string) return error.LengthMismatch;
|
|
|
|
|
inline for (std.meta.fields(T)) |field| {
|
|
|
|
|
if (std.mem.eql(u8, field.name, tag.string)) {
|
|
|
|
|
const inner_source = o.get(field.name) orelse return error.MissingField;
|
|
|
|
|
const inner: field.type = std.json.innerParseFromValue(field.type, allocator, inner_source, options) catch |err| {
|
|
|
|
|
std.log.warn("failed to interpret {s} as a {s}: {}", .{ tag.string, @typeName(field.type), err });
|
|
|
|
|
return err;
|
|
|
|
|
};
|
|
|
|
|
return .{ .value = @unionInit(T, field.name, inner) };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return error.InvalidEnumTag;
|
2024-02-28 15:47:37 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn NeverNull(comptime T: type, comptime default_value: T) type {
|
|
|
|
|
return struct {
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
|
|
value: T = default_value,
|
|
|
|
|
|
|
|
|
|
pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !Self {
|
|
|
|
|
return .{ .value = (try std.json.innerParse(?T, allocator, source, options)) orelse default_value };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn jsonParseFromValue(allocator: std.mem.Allocator, source: std.json.Value, options: std.json.ParseOptions) !Self {
|
|
|
|
|
return .{ .value = (try std.json.innerParseFromValue(?T, allocator, source, options)) orelse default_value };
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn fillDefaultStructValues(comptime T: type, r: *T) !void {
|
|
|
|
|
inline for (@typeInfo(T).Struct.fields) |field| {
|
|
|
|
|
if (field.default_value) |default_ptr| {
|
|
|
|
|
if (@field(r, field.name) == null) {
|
|
|
|
|
const default = @as(*align(1) const field.type, @ptrCast(default_ptr)).*;
|
|
|
|
|
@field(r, field.name) = default;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|