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
This commit is contained in:
Dmitry Matveyev 2021-08-20 17:52:48 +06:00 committed by GitHub
parent cfb2827b0a
commit b2e970d157
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 132 additions and 5 deletions

View File

@ -1757,6 +1757,7 @@ test "parseUnsigned" {
}
pub const parseFloat = @import("fmt/parse_float.zig").parseFloat;
pub const ParseFloatError = @import("fmt/parse_float.zig").ParseFloatError;
pub const parseHexFloat = @import("fmt/parse_hex_float.zig").parseHexFloat;
test {

View File

@ -349,7 +349,9 @@ fn caseInEql(a: []const u8, b: []const u8) bool {
return true;
}
pub fn parseFloat(comptime T: type, s: []const u8) !T {
pub const ParseFloatError = error{InvalidCharacter};
pub fn parseFloat(comptime T: type, s: []const u8) ParseFloatError!T {
if (s.len == 0 or (s.len == 1 and (s[0] == '+' or s[0] == '-'))) {
return error.InvalidCharacter;
}

View File

@ -1468,7 +1468,9 @@ pub const ParseOptions = struct {
allow_trailing_data: bool = false,
};
fn skipValue(tokens: *TokenStream) !void {
const SkipValueError = error{UnexpectedJsonDepth} || TokenStream.Error;
fn skipValue(tokens: *TokenStream) SkipValueError!void {
const original_depth = tokens.stackUsed();
// Return an error if no value is found
@ -1530,7 +1532,84 @@ test "skipValue" {
}
}
fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: ParseOptions) !T {
fn ParseInternalError(comptime T: type) type {
// `inferred_types` is used to avoid infinite recursion for recursive type definitions.
const inferred_types = [_]type{};
return ParseInternalErrorImpl(T, &inferred_types);
}
fn ParseInternalErrorImpl(comptime T: type, comptime inferred_types: []const type) type {
for (inferred_types) |ty| {
if (T == ty) return error{};
}
switch (@typeInfo(T)) {
.Bool => return error{UnexpectedToken},
.Float, .ComptimeFloat => return error{UnexpectedToken} || std.fmt.ParseFloatError,
.Int, .ComptimeInt => {
return error{ UnexpectedToken, InvalidNumber, Overflow } ||
std.fmt.ParseIntError || std.fmt.ParseFloatError;
},
.Optional => |optionalInfo| {
return ParseInternalErrorImpl(optionalInfo.child, inferred_types ++ [_]type{T});
},
.Enum => return error{ UnexpectedToken, InvalidEnumTag } || std.fmt.ParseIntError ||
std.meta.IntToEnumError || std.meta.IntToEnumError,
.Union => |unionInfo| {
if (unionInfo.tag_type) |_| {
var errors = error{NoUnionMembersMatched};
for (unionInfo.fields) |u_field| {
errors = errors || ParseInternalErrorImpl(u_field.field_type, inferred_types ++ [_]type{T});
}
return errors;
} else {
@compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
}
},
.Struct => |structInfo| {
var errors = error{
DuplicateJSONField,
UnexpectedEndOfJson,
UnexpectedToken,
UnexpectedValue,
UnknownField,
MissingField,
} || SkipValueError || TokenStream.Error;
for (structInfo.fields) |field| {
errors = errors || ParseInternalErrorImpl(field.field_type, inferred_types ++ [_]type{T});
}
return errors;
},
.Array => |arrayInfo| {
return error{ UnexpectedEndOfJson, UnexpectedToken } || TokenStream.Error ||
UnescapeValidStringError ||
ParseInternalErrorImpl(arrayInfo.child, inferred_types ++ [_]type{T});
},
.Pointer => |ptrInfo| {
var errors = error{AllocatorRequired} || std.mem.Allocator.Error;
switch (ptrInfo.size) {
.One => {
return errors || ParseInternalErrorImpl(ptrInfo.child, inferred_types ++ [_]type{T});
},
.Slice => {
return errors || error{ UnexpectedEndOfJson, UnexpectedToken } ||
ParseInternalErrorImpl(ptrInfo.child, inferred_types ++ [_]type{T}) ||
UnescapeValidStringError || TokenStream.Error;
},
else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
}
},
else => return error{},
}
unreachable;
}
fn parseInternal(
comptime T: type,
token: Token,
tokens: *TokenStream,
options: ParseOptions,
) ParseInternalError(T)!T {
switch (@typeInfo(T)) {
.Bool => {
return switch (token) {
@ -1794,7 +1873,11 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
unreachable;
}
pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) !T {
pub fn ParseError(comptime T: type) type {
return ParseInternalError(T) || error{UnexpectedEndOfJson} || TokenStream.Error;
}
pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) ParseError(T)!T {
const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
const r = try parseInternal(T, token, tokens, options);
errdefer parseFree(T, r, options);
@ -2181,6 +2264,45 @@ test "parse into struct ignoring unknown fields" {
try testing.expectEqualSlices(u8, "zig", r.language);
}
const ParseIntoRecursiveUnionDefinitionValue = union(enum) {
integer: i64,
array: []const ParseIntoRecursiveUnionDefinitionValue,
};
test "parse into recursive union definition" {
const T = struct {
values: ParseIntoRecursiveUnionDefinitionValue,
};
const ops = ParseOptions{ .allocator = testing.allocator };
const r = try parse(T, &std.json.TokenStream.init("{\"values\":[58]}"), ops);
defer parseFree(T, r, ops);
try testing.expectEqual(@as(i64, 58), r.values.array[0].integer);
}
const ParseIntoDoubleRecursiveUnionValueFirst = union(enum) {
integer: i64,
array: []const ParseIntoDoubleRecursiveUnionValueSecond,
};
const ParseIntoDoubleRecursiveUnionValueSecond = union(enum) {
boolean: bool,
array: []const ParseIntoDoubleRecursiveUnionValueFirst,
};
test "parse into double recursive union definition" {
const T = struct {
values: ParseIntoDoubleRecursiveUnionValueFirst,
};
const ops = ParseOptions{ .allocator = testing.allocator };
const r = try parse(T, &std.json.TokenStream.init("{\"values\":[[58]]}"), ops);
defer parseFree(T, r, ops);
try testing.expectEqual(@as(i64, 58), r.values.array[0].array[0].integer);
}
/// A non-stream JSON parser which constructs a tree of Value's.
pub const Parser = struct {
allocator: *Allocator,
@ -2418,10 +2540,12 @@ pub const Parser = struct {
}
};
pub const UnescapeValidStringError = error{InvalidUnicodeHexSymbol};
/// Unescape a JSON string
/// Only to be used on strings already validated by the parser
/// (note the unreachable statements and lack of bounds checking)
pub fn unescapeValidString(output: []u8, input: []const u8) !void {
pub fn unescapeValidString(output: []u8, input: []const u8) UnescapeValidStringError!void {
var inIndex: usize = 0;
var outIndex: usize = 0;