diff --git a/lib/std/json.zig b/lib/std/json.zig index 2997a4085413..c17b83d12efd 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -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{}; @@ -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, @@ -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)),