mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
Merge pull request #7918 from EthanGruffudd/json-ignore-fields
Add option to ignore unknown fields when parsing json
This commit is contained in:
commit
11ae6c42c1
1 changed files with 117 additions and 1 deletions
118
lib/std/json.zig
118
lib/std/json.zig
|
|
@ -1105,6 +1105,10 @@ pub const TokenStream = struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stackUsed(self: *TokenStream) u8 {
|
||||||
|
return self.parser.stack_used + if (self.token != null) @as(u8, 1) else 0;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn next(self: *TokenStream) Error!?Token {
|
pub fn next(self: *TokenStream) Error!?Token {
|
||||||
if (self.token) |token| {
|
if (self.token) |token| {
|
||||||
self.token = null;
|
self.token = null;
|
||||||
|
|
@ -1457,8 +1461,73 @@ pub const ParseOptions = struct {
|
||||||
Error,
|
Error,
|
||||||
UseLast,
|
UseLast,
|
||||||
} = .Error,
|
} = .Error,
|
||||||
|
|
||||||
|
/// If false, finding an unknown field returns an error.
|
||||||
|
ignore_unknown_fields: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn skipValue(tokens: *TokenStream) !void {
|
||||||
|
const original_depth = tokens.stackUsed();
|
||||||
|
|
||||||
|
// Return an error if no value is found
|
||||||
|
_ = try tokens.next();
|
||||||
|
if (tokens.stackUsed() < original_depth) return error.UnexpectedJsonDepth;
|
||||||
|
if (tokens.stackUsed() == original_depth) return;
|
||||||
|
|
||||||
|
while (try tokens.next()) |_| {
|
||||||
|
if (tokens.stackUsed() == original_depth) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "skipValue" {
|
||||||
|
try skipValue(&TokenStream.init("false"));
|
||||||
|
try skipValue(&TokenStream.init("true"));
|
||||||
|
try skipValue(&TokenStream.init("null"));
|
||||||
|
try skipValue(&TokenStream.init("42"));
|
||||||
|
try skipValue(&TokenStream.init("42.0"));
|
||||||
|
try skipValue(&TokenStream.init("\"foo\""));
|
||||||
|
try skipValue(&TokenStream.init("[101, 111, 121]"));
|
||||||
|
try skipValue(&TokenStream.init("{}"));
|
||||||
|
try skipValue(&TokenStream.init("{\"foo\": \"bar\"}"));
|
||||||
|
|
||||||
|
{ // An absurd number of nestings
|
||||||
|
const nestings = 256;
|
||||||
|
|
||||||
|
try testing.expectError(
|
||||||
|
error.TooManyNestedItems,
|
||||||
|
skipValue(&TokenStream.init("[" ** nestings ++ "]" ** nestings)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Would a number token cause problems in a deeply-nested array?
|
||||||
|
const nestings = 255;
|
||||||
|
const deeply_nested_array = "[" ** nestings ++ "0.118, 999, 881.99, 911.9, 725, 3" ++ "]" ** nestings;
|
||||||
|
|
||||||
|
try skipValue(&TokenStream.init(deeply_nested_array));
|
||||||
|
|
||||||
|
try testing.expectError(
|
||||||
|
error.TooManyNestedItems,
|
||||||
|
skipValue(&TokenStream.init("[" ++ deeply_nested_array ++ "]")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mismatched brace/square bracket
|
||||||
|
try testing.expectError(
|
||||||
|
error.UnexpectedClosingBrace,
|
||||||
|
skipValue(&TokenStream.init("[102, 111, 111}")),
|
||||||
|
);
|
||||||
|
|
||||||
|
{ // should fail if no value found (e.g. immediate close of object)
|
||||||
|
var empty_object = TokenStream.init("{}");
|
||||||
|
assert(.ObjectBegin == (try empty_object.next()).?);
|
||||||
|
try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_object));
|
||||||
|
|
||||||
|
var empty_array = TokenStream.init("[]");
|
||||||
|
assert(.ArrayBegin == (try empty_array.next()).?);
|
||||||
|
try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_array));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: ParseOptions) !T {
|
fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: ParseOptions) !T {
|
||||||
switch (@typeInfo(T)) {
|
switch (@typeInfo(T)) {
|
||||||
.Bool => {
|
.Bool => {
|
||||||
|
|
@ -1598,7 +1667,14 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) return error.UnknownField;
|
if (!found) {
|
||||||
|
if (options.ignore_unknown_fields) {
|
||||||
|
try skipValue(tokens);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return error.UnknownField;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
else => return error.UnexpectedToken,
|
else => return error.UnexpectedToken,
|
||||||
}
|
}
|
||||||
|
|
@ -2040,6 +2116,46 @@ test "parse into struct with duplicate field" {
|
||||||
try testing.expectError(error.UnexpectedValue, parse(T3, &TokenStream.init(str), options_last));
|
try testing.expectError(error.UnexpectedValue, parse(T3, &TokenStream.init(str), options_last));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "parse into struct ignoring unknown fields" {
|
||||||
|
const T = struct {
|
||||||
|
int: i64,
|
||||||
|
language: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ops = ParseOptions{
|
||||||
|
.allocator = testing.allocator,
|
||||||
|
.ignore_unknown_fields = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const r = try parse(T, &std.json.TokenStream.init(
|
||||||
|
\\{
|
||||||
|
\\ "int": 420,
|
||||||
|
\\ "float": 3.14,
|
||||||
|
\\ "with\\escape": true,
|
||||||
|
\\ "with\u0105unicode\ud83d\ude02": false,
|
||||||
|
\\ "optional": null,
|
||||||
|
\\ "static_array": [66.6, 420.420, 69.69],
|
||||||
|
\\ "dynamic_array": [66.6, 420.420, 69.69],
|
||||||
|
\\ "complex": {
|
||||||
|
\\ "nested": "zig"
|
||||||
|
\\ },
|
||||||
|
\\ "veryComplex": [
|
||||||
|
\\ {
|
||||||
|
\\ "foo": "zig"
|
||||||
|
\\ }, {
|
||||||
|
\\ "foo": "rocks"
|
||||||
|
\\ }
|
||||||
|
\\ ],
|
||||||
|
\\ "a_union": 100000,
|
||||||
|
\\ "language": "zig"
|
||||||
|
\\}
|
||||||
|
), ops);
|
||||||
|
defer parseFree(T, r, ops);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(i64, 420), r.int);
|
||||||
|
try testing.expectEqualSlices(u8, "zig", r.language);
|
||||||
|
}
|
||||||
|
|
||||||
/// A non-stream JSON parser which constructs a tree of Value's.
|
/// A non-stream JSON parser which constructs a tree of Value's.
|
||||||
pub const Parser = struct {
|
pub const Parser = struct {
|
||||||
allocator: *Allocator,
|
allocator: *Allocator,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue