authorgravatar for greenfork.lists@yandex.comDmitry Matveyev <greenfork.lists@yandex.com> 2021-08-20 17:52:48+06:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-08-20 14:52:48+03:00
logb2e970d157343919ea2be44b3ebe4c519324bd4e
tree0e2f22cd16d360e36cdf9edbeafb5b1aa882e49e
parentcfb2827b0a9efaf3937db27dbe3352cbb612466b
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.json: Add support for recursive objects to std.json.parse (#9307)

* Add support for recursive objects to std.json.parse * Remove previously defined error set * Try with function which returns an error set * Don't analyze already inferred types * Add comptime to inferred_type parameter * Make ParseInternalError to accept only a single argument * Add public `ParseError` for `parse` function * Use error.Foo syntax for errors instead of a named error set * Better formatting * Update to latest code changes

3 files changed, 132 insertions(+), 5 deletions(-)

lib/std/fmt.zig+1
...@@ -1757,6 +1757,7 @@ test "parseUnsigned" {...@@ -1757,6 +1757,7 @@ test "parseUnsigned" {
1757}1757}
17581758
1759pub const parseFloat = @import("fmt/parse_float.zig").parseFloat;1759pub const parseFloat = @import("fmt/parse_float.zig").parseFloat;
1760pub const ParseFloatError = @import("fmt/parse_float.zig").ParseFloatError;
1760pub const parseHexFloat = @import("fmt/parse_hex_float.zig").parseHexFloat;1761pub const parseHexFloat = @import("fmt/parse_hex_float.zig").parseHexFloat;
17611762
1762test {1763test {
lib/std/fmt/parse_float.zig+3-1
...@@ -349,7 +349,9 @@ fn caseInEql(a: []const u8, b: []const u8) bool {...@@ -349,7 +349,9 @@ fn caseInEql(a: []const u8, b: []const u8) bool {
349 return true;349 return true;
350}350}
351351
352pub fn parseFloat(comptime T: type, s: []const u8) !T {352pub const ParseFloatError = error{InvalidCharacter};
353
354pub fn parseFloat(comptime T: type, s: []const u8) ParseFloatError!T {
353 if (s.len == 0 or (s.len == 1 and (s[0] == '+' or s[0] == '-'))) {355 if (s.len == 0 or (s.len == 1 and (s[0] == '+' or s[0] == '-'))) {
354 return error.InvalidCharacter;356 return error.InvalidCharacter;
355 }357 }
lib/std/json.zig+128-4
...@@ -1468,7 +1468,9 @@ pub const ParseOptions = struct {...@@ -1468,7 +1468,9 @@ pub const ParseOptions = struct {
1468 allow_trailing_data: bool = false,1468 allow_trailing_data: bool = false,
1469};1469};
14701470
1471fn skipValue(tokens: *TokenStream) !void {1471const SkipValueError = error{UnexpectedJsonDepth} || TokenStream.Error;
1472
1473fn skipValue(tokens: *TokenStream) SkipValueError!void {
1472 const original_depth = tokens.stackUsed();1474 const original_depth = tokens.stackUsed();
14731475
1474 // Return an error if no value is found1476 // Return an error if no value is found
...@@ -1530,7 +1532,84 @@ test "skipValue" {...@@ -1530,7 +1532,84 @@ test "skipValue" {
1530 }1532 }
1531}1533}
15321534
1533fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: ParseOptions) !T {1535fn ParseInternalError(comptime T: type) type {
1536 // `inferred_types` is used to avoid infinite recursion for recursive type definitions.
1537 const inferred_types = [_]type{};
1538 return ParseInternalErrorImpl(T, &inferred_types);
1539}
1540
1541fn ParseInternalErrorImpl(comptime T: type, comptime inferred_types: []const type) type {
1542 for (inferred_types) |ty| {
1543 if (T == ty) return error{};
1544 }
1545
1546 switch (@typeInfo(T)) {
1547 .Bool => return error{UnexpectedToken},
1548 .Float, .ComptimeFloat => return error{UnexpectedToken} || std.fmt.ParseFloatError,
1549 .Int, .ComptimeInt => {
1550 return error{ UnexpectedToken, InvalidNumber, Overflow } ||
1551 std.fmt.ParseIntError || std.fmt.ParseFloatError;
1552 },
1553 .Optional => |optionalInfo| {
1554 return ParseInternalErrorImpl(optionalInfo.child, inferred_types ++ [_]type{T});
1555 },
1556 .Enum => return error{ UnexpectedToken, InvalidEnumTag } || std.fmt.ParseIntError ||
1557 std.meta.IntToEnumError || std.meta.IntToEnumError,
1558 .Union => |unionInfo| {
1559 if (unionInfo.tag_type) |_| {
1560 var errors = error{NoUnionMembersMatched};
1561 for (unionInfo.fields) |u_field| {
1562 errors = errors || ParseInternalErrorImpl(u_field.field_type, inferred_types ++ [_]type{T});
1563 }
1564 return errors;
1565 } else {
1566 @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
1567 }
1568 },
1569 .Struct => |structInfo| {
1570 var errors = error{
1571 DuplicateJSONField,
1572 UnexpectedEndOfJson,
1573 UnexpectedToken,
1574 UnexpectedValue,
1575 UnknownField,
1576 MissingField,
1577 } || SkipValueError || TokenStream.Error;
1578 for (structInfo.fields) |field| {
1579 errors = errors || ParseInternalErrorImpl(field.field_type, inferred_types ++ [_]type{T});
1580 }
1581 return errors;
1582 },
1583 .Array => |arrayInfo| {
1584 return error{ UnexpectedEndOfJson, UnexpectedToken } || TokenStream.Error ||
1585 UnescapeValidStringError ||
1586 ParseInternalErrorImpl(arrayInfo.child, inferred_types ++ [_]type{T});
1587 },
1588 .Pointer => |ptrInfo| {
1589 var errors = error{AllocatorRequired} || std.mem.Allocator.Error;
1590 switch (ptrInfo.size) {
1591 .One => {
1592 return errors || ParseInternalErrorImpl(ptrInfo.child, inferred_types ++ [_]type{T});
1593 },
1594 .Slice => {
1595 return errors || error{ UnexpectedEndOfJson, UnexpectedToken } ||
1596 ParseInternalErrorImpl(ptrInfo.child, inferred_types ++ [_]type{T}) ||
1597 UnescapeValidStringError || TokenStream.Error;
1598 },
1599 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
1600 }
1601 },
1602 else => return error{},
1603 }
1604 unreachable;
1605}
1606
1607fn parseInternal(
1608 comptime T: type,
1609 token: Token,
1610 tokens: *TokenStream,
1611 options: ParseOptions,
1612) ParseInternalError(T)!T {
1534 switch (@typeInfo(T)) {1613 switch (@typeInfo(T)) {
1535 .Bool => {1614 .Bool => {
1536 return switch (token) {1615 return switch (token) {
...@@ -1794,7 +1873,11 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:...@@ -1794,7 +1873,11 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
1794 unreachable;1873 unreachable;
1795}1874}
17961875
1797pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) !T {1876pub fn ParseError(comptime T: type) type {
1877 return ParseInternalError(T) || error{UnexpectedEndOfJson} || TokenStream.Error;
1878}
1879
1880pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) ParseError(T)!T {
1798 const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;1881 const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
1799 const r = try parseInternal(T, token, tokens, options);1882 const r = try parseInternal(T, token, tokens, options);
1800 errdefer parseFree(T, r, options);1883 errdefer parseFree(T, r, options);
...@@ -2181,6 +2264,45 @@ test "parse into struct ignoring unknown fields" {...@@ -2181,6 +2264,45 @@ test "parse into struct ignoring unknown fields" {
2181 try testing.expectEqualSlices(u8, "zig", r.language);2264 try testing.expectEqualSlices(u8, "zig", r.language);
2182}2265}
21832266
2267const ParseIntoRecursiveUnionDefinitionValue = union(enum) {
2268 integer: i64,
2269 array: []const ParseIntoRecursiveUnionDefinitionValue,
2270};
2271
2272test "parse into recursive union definition" {
2273 const T = struct {
2274 values: ParseIntoRecursiveUnionDefinitionValue,
2275 };
2276 const ops = ParseOptions{ .allocator = testing.allocator };
2277
2278 const r = try parse(T, &std.json.TokenStream.init("{\"values\":[58]}"), ops);
2279 defer parseFree(T, r, ops);
2280
2281 try testing.expectEqual(@as(i64, 58), r.values.array[0].integer);
2282}
2283
2284const ParseIntoDoubleRecursiveUnionValueFirst = union(enum) {
2285 integer: i64,
2286 array: []const ParseIntoDoubleRecursiveUnionValueSecond,
2287};
2288
2289const ParseIntoDoubleRecursiveUnionValueSecond = union(enum) {
2290 boolean: bool,
2291 array: []const ParseIntoDoubleRecursiveUnionValueFirst,
2292};
2293
2294test "parse into double recursive union definition" {
2295 const T = struct {
2296 values: ParseIntoDoubleRecursiveUnionValueFirst,
2297 };
2298 const ops = ParseOptions{ .allocator = testing.allocator };
2299
2300 const r = try parse(T, &std.json.TokenStream.init("{\"values\":[[58]]}"), ops);
2301 defer parseFree(T, r, ops);
2302
2303 try testing.expectEqual(@as(i64, 58), r.values.array[0].array[0].integer);
2304}
2305
2184/// A non-stream JSON parser which constructs a tree of Value's.2306/// A non-stream JSON parser which constructs a tree of Value's.
2185pub const Parser = struct {2307pub const Parser = struct {
2186 allocator: *Allocator,2308 allocator: *Allocator,
...@@ -2418,10 +2540,12 @@ pub const Parser = struct {...@@ -2418,10 +2540,12 @@ pub const Parser = struct {
2418 }2540 }
2419};2541};
24202542
2543pub const UnescapeValidStringError = error{InvalidUnicodeHexSymbol};
2544
2421/// Unescape a JSON string2545/// Unescape a JSON string
2422/// Only to be used on strings already validated by the parser2546/// Only to be used on strings already validated by the parser
2423/// (note the unreachable statements and lack of bounds checking)2547/// (note the unreachable statements and lack of bounds checking)
2424pub fn unescapeValidString(output: []u8, input: []const u8) !void {2548pub fn unescapeValidString(output: []u8, input: []const u8) UnescapeValidStringError!void {
2425 var inIndex: usize = 0;2549 var inIndex: usize = 0;
2426 var outIndex: usize = 0;2550 var outIndex: usize = 0;
24272551