Skip to content
Closed
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
63 changes: 61 additions & 2 deletions lib/std/json.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,54 @@ test "skipValue" {
}
}

test "field aliases" {
const S = struct {
value: u8,
a: u8,
z: u8,
pub const __field_aliases = .{
.value = "__value",
.a = "__a",
.z = "__z",
};
};
const text =
\\{"__value": 42, "__a": 43, "__z": 44}
;

const s = try parse(S, &TokenStream.init(text), .{});
try testing.expectEqual(@as(u8, 42), s.value);
try testing.expectEqual(@as(u8, 43), s.a);
try testing.expectEqual(@as(u8, 44), s.z);
}

test "field alias escapes" {
const text =
\\{"__f\u006fo": 42}
;

{
// match field, mismatched alias
const S = struct {
foo: u8,
pub const __field_aliases = .{ .foo = "__bar" };
};
try testing.expectError(
error.UnknownField,
parse(S, &TokenStream.init(text), .{}),
);
}
{
// match field, match alias
const S = struct {
foo: u8,
pub const __field_aliases = .{ .foo = "__foo" };
};
const s = try parse(S, &TokenStream.init(text), .{});
try testing.expectEqual(@as(u8, 42), s.foo);
}
}

fn ParseInternalError(comptime T: type) type {
// `inferred_types` is used to avoid infinite recursion for recursive type definitions.
const inferred_types = [_]type{};
Expand Down Expand Up @@ -1730,6 +1778,10 @@ fn parseInternal(
}
}

// support for field_aliases
const FieldAliases = std.enums.EnumFieldStruct(T, ?[*:0]const u8, @as(?[*:0]const u8, null));
const field_aliases: FieldAliases = if (@hasDecl(T, "__field_aliases")) T.__field_aliases else .{};

while (true) {
switch ((try tokens.next()) orelse return error.UnexpectedEndOfJson) {
.ObjectEnd => break,
Expand All @@ -1739,8 +1791,15 @@ fn parseInternal(
child_options.allow_trailing_data = true;
var found = false;
inline for (structInfo.fields) |field, i| {
// TODO: using switches here segfault the compiler (#2727?)
if ((stringToken.escapes == .None and mem.eql(u8, field.name, key_source_slice)) or (stringToken.escapes == .Some and (field.name.len == stringToken.decodedLength() and encodesTo(field.name, key_source_slice)))) {
const field_name = comptime if (@field(field_aliases, field.name)) |alias|
mem.span(alias)
else
field.name;

if ((stringToken.escapes == .None and mem.eql(u8, field_name, key_source_slice)) or
(stringToken.escapes == .Some and ((field_name.len == stringToken.decodedLength() and encodesTo(field_name, key_source_slice)))))
{
// TODO: using switches here segfault the compiler (#2727?)
// if (switch (stringToken.escapes) {
// .None => mem.eql(u8, field.name, key_source_slice),
// .Some => (field.name.len == stringToken.decodedLength() and encodesTo(field.name, key_source_slice)),
Expand Down