authorgravatar for quae@daurnimator.comdaurnimator <quae@daurnimator.com> 2021-02-01 00:55:22+11:00
committergravatar for quae@daurnimator.comdaurnimator <quae@daurnimator.com> 2021-02-01 01:01:50+11:00
loge0a04e7f6739aa0d81e3102c04af37ab51e550d4
treeb92180bd138c69b18937f9e407702711e8f31b51
parentf88bb56ee5be01b83ab35bd7b6c95539a4e04a9d
signaturelock-open Commit is signed but in an unrecognized format.

allow more complex comptime fields in std.json


1 files changed, 94 insertions(+), 13 deletions(-)

lib/std/json.zig+94-13
...@@ -1374,6 +1374,65 @@ test "Value.jsonStringify" {...@@ -1374,6 +1374,65 @@ test "Value.jsonStringify" {
1374 }1374 }
1375}1375}
13761376
1377/// parse tokens from a stream, returning `false` if they do not decode to `value`
1378fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool {
1379 // TODO: should be able to write this function to not require an allocator
1380 const tmp = try parse(T, tokens, options);
1381 defer parseFree(T, tmp, options);
1382
1383 return parsedEqual(tmp, value);
1384}
1385
1386/// Returns if a value returned by `parse` is deep-equal to another value
1387fn parsedEqual(a: anytype, b: @TypeOf(a)) bool {
1388 switch (@typeInfo(@TypeOf(a))) {
1389 .Optional => {
1390 if (a == null and b == null) return true;
1391 if (a == null or b == null) return false;
1392 return parsedEqual(a.?, b.?);
1393 },
1394 .Union => |unionInfo| {
1395 if (info.tag_type) |UnionTag| {
1396 const tag_a = std.meta.activeTag(a);
1397 const tag_b = std.meta.activeTag(b);
1398 if (tag_a != tag_b) return false;
1399
1400 inline for (info.fields) |field_info| {
1401 if (@field(UnionTag, field_info.name) == tag_a) {
1402 return parsedEqual(@field(a, field_info.name), @field(b, field_info.name));
1403 }
1404 }
1405 return false;
1406 } else {
1407 unreachable;
1408 }
1409 },
1410 .Array => {
1411 for (a) |e, i|
1412 if (!parsedEqual(e, b[i])) return false;
1413 return true;
1414 },
1415 .Struct => |info| {
1416 inline for (info.fields) |field_info| {
1417 if (!parsedEqual(@field(a, field_info.name), @field(b, field_info.name))) return false;
1418 }
1419 return true;
1420 },
1421 .Pointer => |ptrInfo| switch (ptrInfo.size) {
1422 .One => return parsedEqual(a.*, b.*),
1423 .Slice => {
1424 if (a.len != b.len) return false;
1425 for (a) |e, i|
1426 if (!parsedEqual(e, b[i])) return false;
1427 return true;
1428 },
1429 .Many, .C => unreachable,
1430 },
1431 else => return a == b,
1432 }
1433 unreachable;
1434}
1435
1377pub const ParseOptions = struct {1436pub const ParseOptions = struct {
1378 allocator: ?*Allocator = null,1437 allocator: ?*Allocator = null,
13791438
...@@ -1507,9 +1566,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:...@@ -1507,9 +1566,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
1507 }1566 }
1508 }1567 }
1509 if (field.is_comptime) {1568 if (field.is_comptime) {
1510 const value = try parse(field.field_type, tokens, options);1569 if (!try parsesTo(field.field_type, field.default_value.?, tokens, options)) {
1511 defer parseFree(field.field_type, value, options);
1512 if (value != @field(r, field.name)) {
1513 return error.UnexpectedValue;1570 return error.UnexpectedValue;
1514 }1571 }
1515 } else {1572 } else {
...@@ -1809,16 +1866,40 @@ test "parseFree descends into tagged union" {...@@ -1809,16 +1866,40 @@ test "parseFree descends into tagged union" {
1809}1866}
18101867
1811test "parse with comptime field" {1868test "parse with comptime field" {
1812 const T = struct {1869 {
1813 comptime a: i32 = 0,1870 const T = struct {
1814 b: bool,1871 comptime a: i32 = 0,
1815 };1872 b: bool,
1816 testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &TokenStream.init(1873 };
1817 \\{1874 testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &TokenStream.init(
1818 \\ "a": 0,1875 \\{
1819 \\ "b": true1876 \\ "a": 0,
1820 \\}1877 \\ "b": true
1821 ), ParseOptions{}));1878 \\}
1879 ), ParseOptions{}));
1880 }
1881
1882 { // string comptime values currently require an allocator
1883 const T = union(enum) {
1884 foo: struct {
1885 comptime kind: []const u8 = "boolean",
1886 b: bool,
1887 },
1888 bar: struct {
1889 comptime kind: []const u8 = "float",
1890 b: f64,
1891 },
1892 };
1893
1894 const r = try std.json.parse(T, &std.json.TokenStream.init(
1895 \\{
1896 \\ "kind": "float",
1897 \\ "b": 1.0
1898 \\}
1899 ), .{
1900 .allocator = std.testing.allocator,
1901 });
1902 }
1822}1903}
18231904
1824test "parse into struct with no fields" {1905test "parse into struct with no fields" {