| ... | ... | @@ -1105,6 +1105,10 @@ pub const TokenStream = struct { |
| 1105 | 1105 | }; |
| 1106 | 1106 | } |
| 1107 | 1107 | |
| 1108 | fn stackUsed(self: *TokenStream) u8 { |
| 1109 | return self.parser.stack_used + if (self.token != null) @as(u8, 1) else 0; |
| 1110 | } |
| 1111 | |
| 1108 | 1112 | pub fn next(self: *TokenStream) Error!?Token { |
| 1109 | 1113 | if (self.token) |token| { |
| 1110 | 1114 | self.token = null; |
| ... | ... | @@ -1457,8 +1461,73 @@ pub const ParseOptions = struct { |
| 1457 | 1461 | Error, |
| 1458 | 1462 | UseLast, |
| 1459 | 1463 | } = .Error, |
| 1464 | |
| 1465 | /// If false, finding an unknown field returns an error. |
| 1466 | ignore_unknown_fields: bool = false, |
| 1460 | 1467 | }; |
| 1461 | 1468 | |
| 1469 | fn skipValue(tokens: *TokenStream) !void { |
| 1470 | const original_depth = tokens.stackUsed(); |
| 1471 | |
| 1472 | // Return an error if no value is found |
| 1473 | _ = try tokens.next(); |
| 1474 | if (tokens.stackUsed() < original_depth) return error.UnexpectedJsonDepth; |
| 1475 | if (tokens.stackUsed() == original_depth) return; |
| 1476 | |
| 1477 | while (try tokens.next()) |_| { |
| 1478 | if (tokens.stackUsed() == original_depth) return; |
| 1479 | } |
| 1480 | } |
| 1481 | |
| 1482 | test "skipValue" { |
| 1483 | try skipValue(&TokenStream.init("false")); |
| 1484 | try skipValue(&TokenStream.init("true")); |
| 1485 | try skipValue(&TokenStream.init("null")); |
| 1486 | try skipValue(&TokenStream.init("42")); |
| 1487 | try skipValue(&TokenStream.init("42.0")); |
| 1488 | try skipValue(&TokenStream.init("\"foo\"")); |
| 1489 | try skipValue(&TokenStream.init("[101, 111, 121]")); |
| 1490 | try skipValue(&TokenStream.init("{}")); |
| 1491 | try skipValue(&TokenStream.init("{\"foo\": \"bar\"}")); |
| 1492 | |
| 1493 | { // An absurd number of nestings |
| 1494 | const nestings = 256; |
| 1495 | |
| 1496 | try testing.expectError( |
| 1497 | error.TooManyNestedItems, |
| 1498 | skipValue(&TokenStream.init("[" ** nestings ++ "]" ** nestings)), |
| 1499 | ); |
| 1500 | } |
| 1501 | |
| 1502 | { // Would a number token cause problems in a deeply-nested array? |
| 1503 | const nestings = 255; |
| 1504 | const deeply_nested_array = "[" ** nestings ++ "0.118, 999, 881.99, 911.9, 725, 3" ++ "]" ** nestings; |
| 1505 | |
| 1506 | try skipValue(&TokenStream.init(deeply_nested_array)); |
| 1507 | |
| 1508 | try testing.expectError( |
| 1509 | error.TooManyNestedItems, |
| 1510 | skipValue(&TokenStream.init("[" ++ deeply_nested_array ++ "]")), |
| 1511 | ); |
| 1512 | } |
| 1513 | |
| 1514 | // Mismatched brace/square bracket |
| 1515 | try testing.expectError( |
| 1516 | error.UnexpectedClosingBrace, |
| 1517 | skipValue(&TokenStream.init("[102, 111, 111}")), |
| 1518 | ); |
| 1519 | |
| 1520 | { // should fail if no value found (e.g. immediate close of object) |
| 1521 | var empty_object = TokenStream.init("{}"); |
| 1522 | assert(.ObjectBegin == (try empty_object.next()).?); |
| 1523 | try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_object)); |
| 1524 | |
| 1525 | var empty_array = TokenStream.init("[]"); |
| 1526 | assert(.ArrayBegin == (try empty_array.next()).?); |
| 1527 | try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_array)); |
| 1528 | } |
| 1529 | } |
| 1530 | |
| 1462 | 1531 | fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: ParseOptions) !T { |
| 1463 | 1532 | switch (@typeInfo(T)) { |
| 1464 | 1533 | .Bool => { |
| ... | ... | @@ -1598,7 +1667,14 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1598 | 1667 | break; |
| 1599 | 1668 | } |
| 1600 | 1669 | } |
| 1601 | | if (!found) return error.UnknownField; |
| 1670 | if (!found) { |
| 1671 | if (options.ignore_unknown_fields) { |
| 1672 | try skipValue(tokens); |
| 1673 | continue; |
| 1674 | } else { |
| 1675 | return error.UnknownField; |
| 1676 | } |
| 1677 | } |
| 1602 | 1678 | }, |
| 1603 | 1679 | else => return error.UnexpectedToken, |
| 1604 | 1680 | } |
| ... | ... | @@ -2040,6 +2116,46 @@ test "parse into struct with duplicate field" { |
| 2040 | 2116 | try testing.expectError(error.UnexpectedValue, parse(T3, &TokenStream.init(str), options_last)); |
| 2041 | 2117 | } |
| 2042 | 2118 | |
| 2119 | test "parse into struct ignoring unknown fields" { |
| 2120 | const T = struct { |
| 2121 | int: i64, |
| 2122 | language: []const u8, |
| 2123 | }; |
| 2124 | |
| 2125 | const ops = ParseOptions{ |
| 2126 | .allocator = testing.allocator, |
| 2127 | .ignore_unknown_fields = true, |
| 2128 | }; |
| 2129 | |
| 2130 | const r = try parse(T, &std.json.TokenStream.init( |
| 2131 | \\{ |
| 2132 | \\ "int": 420, |
| 2133 | \\ "float": 3.14, |
| 2134 | \\ "with\\escape": true, |
| 2135 | \\ "with\u0105unicode\ud83d\ude02": false, |
| 2136 | \\ "optional": null, |
| 2137 | \\ "static_array": [66.6, 420.420, 69.69], |
| 2138 | \\ "dynamic_array": [66.6, 420.420, 69.69], |
| 2139 | \\ "complex": { |
| 2140 | \\ "nested": "zig" |
| 2141 | \\ }, |
| 2142 | \\ "veryComplex": [ |
| 2143 | \\ { |
| 2144 | \\ "foo": "zig" |
| 2145 | \\ }, { |
| 2146 | \\ "foo": "rocks" |
| 2147 | \\ } |
| 2148 | \\ ], |
| 2149 | \\ "a_union": 100000, |
| 2150 | \\ "language": "zig" |
| 2151 | \\} |
| 2152 | ), ops); |
| 2153 | defer parseFree(T, r, ops); |
| 2154 | |
| 2155 | try testing.expectEqual(@as(i64, 420), r.int); |
| 2156 | try testing.expectEqualSlices(u8, "zig", r.language); |
| 2157 | } |
| 2158 | |
| 2043 | 2159 | /// A non-stream JSON parser which constructs a tree of Value's. |
| 2044 | 2160 | pub const Parser = struct { |
| 2045 | 2161 | allocator: *Allocator, |