Skip to content

std.json: support field aliases #8982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 73 additions & 1 deletion lib/std/json.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,73 @@ test "skipValue" {
}
}

// build a list of aliases. entries are [2][]const u8{alias_name, target_field_name}
fn buildFieldAliases(comptime T: type) []const [2][]const u8 {
const tags_decl_name = "__tags";
const alias_field_name = "alias";
// look for `pub const __tags` decl in T
comptime {
const ti = @typeInfo(T);
const tags = if ((ti == .Struct or ti == .Union or ti == .Enum) and
@hasDecl(T, tags_decl_name) and
std.meta.declarationInfo(T, tags_decl_name).is_pub)
@field(T, tags_decl_name)
else
return &.{};

var result: []const [2][]const u8 = &.{};
const field_names = std.meta.fieldNames(T);
for (std.meta.fieldNames(@TypeOf(tags))) |tags_field_name| {
// check if tag field name matches a field name from T
// have to use a function rather than for loop to work around a compiler bug
const found = std.mem.indexOfSliceScalar([]const u8, field_names, tags_field_name) != null;
if (found) {
const tag_member = @field(tags, tags_field_name);
// if the tag_member has an alias field, append this alias, target pair to result
if (@hasField(@TypeOf(tag_member), alias_field_name)) {
result = result ++ [1][2][]const u8{.{ @field(tag_member, alias_field_name), tags_field_name }};
}
}
}
return result;
}
}

fn findFieldAlias(field_aliases: []const [2][]const u8, field_name: []const u8) []const u8 {
for (field_aliases) |alias_pair| {
if (std.mem.eql(u8, alias_pair[0], field_name)) return alias_pair[1];
}
return field_name;
}

test "fieldAliases" {
const S = struct {
value: u8,
pub const __tags = .{
.value = .{ .alias = "__value" },
};
};
const text =
\\{"__value": 42}
;
const s = try parse(S, &TokenStream.init(text), .{});
try testing.expectEqual(@as(u8, 42), s.value);
}

test "fieldAliases __tags missing pub" {
const S = struct {
value: u8,
// missing pub
const __tags = .{
.value = .{ .alias = "__value" },
};
};
const text =
\\{"__value": 42}
;
try std.testing.expectError(error.UnknownField, parse(S, &TokenStream.init(text), .{}));
}

fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: ParseOptions) !T {
switch (@typeInfo(T)) {
.Bool => {
Expand Down Expand Up @@ -1624,11 +1691,16 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
}
}

comptime const field_aliases = buildFieldAliases(T);

while (true) {
switch ((try tokens.next()) orelse return error.UnexpectedEndOfJson) {
.ObjectEnd => break,
.String => |stringToken| {
const key_source_slice = stringToken.slice(tokens.slice, tokens.i - 1);
const key_source_slice = findFieldAlias(
field_aliases,
stringToken.slice(tokens.slice, tokens.i - 1),
);
var child_options = options;
child_options.allow_trailing_data = true;
var found = false;
Expand Down
14 changes: 14 additions & 0 deletions lib/std/mem.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,20 @@ pub fn indexOfPosLinear(comptime T: type, haystack: []const T, start_index: usiz
return null;
}

/// Linear search for the index of a scalar slice value inside a slice of slices.
pub fn indexOfSliceScalar(comptime Slice: type, haystack: []const Slice, needle: Slice) ?usize {
return indexOfSliceScalarPos(Slice, haystack, 0, needle);
}
/// Linear search for the index of a scalar slice value inside a slice of slices starting from start_index.
/// Uses std.mem.eql to check for equality.
pub fn indexOfSliceScalarPos(comptime Slice: type, haystack: []const Slice, start_index: usize, needle: Slice) ?usize {
var i: usize = start_index;
while (i < haystack.len) : (i += 1) {
if (std.mem.eql(std.meta.Child(Slice), haystack[i], needle)) return i;
}
return null;
}

fn boyerMooreHorspoolPreprocessReverse(pattern: []const u8, table: *[256]usize) void {
for (table) |*c| {
c.* = pattern.len;
Expand Down