| ... | ... | @@ -1464,6 +1464,8 @@ pub const ParseOptions = struct { |
| 1464 | 1464 | |
| 1465 | 1465 | /// If false, finding an unknown field returns an error. |
| 1466 | 1466 | ignore_unknown_fields: bool = false, |
| 1467 | |
| 1468 | allow_trailing_data: bool = false, |
| 1467 | 1469 | }; |
| 1468 | 1470 | |
| 1469 | 1471 | fn skipValue(tokens: *TokenStream) !void { |
| ... | ... | @@ -1627,6 +1629,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1627 | 1629 | .ObjectEnd => break, |
| 1628 | 1630 | .String => |stringToken| { |
| 1629 | 1631 | const key_source_slice = stringToken.slice(tokens.slice, tokens.i - 1); |
| 1632 | var child_options = options; |
| 1633 | child_options.allow_trailing_data = true; |
| 1630 | 1634 | var found = false; |
| 1631 | 1635 | inline for (structInfo.fields) |field, i| { |
| 1632 | 1636 | // TODO: using switches here segfault the compiler (#2727?) |
| ... | ... | @@ -1643,24 +1647,24 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1643 | 1647 | // } |
| 1644 | 1648 | if (options.duplicate_field_behavior == .UseFirst) { |
| 1645 | 1649 | // unconditonally ignore value. for comptime fields, this skips check against default_value |
| 1646 | | parseFree(field.field_type, try parse(field.field_type, tokens, options), options); |
| 1650 | parseFree(field.field_type, try parse(field.field_type, tokens, child_options), child_options); |
| 1647 | 1651 | found = true; |
| 1648 | 1652 | break; |
| 1649 | 1653 | } else if (options.duplicate_field_behavior == .Error) { |
| 1650 | 1654 | return error.DuplicateJSONField; |
| 1651 | 1655 | } else if (options.duplicate_field_behavior == .UseLast) { |
| 1652 | 1656 | if (!field.is_comptime) { |
| 1653 | | parseFree(field.field_type, @field(r, field.name), options); |
| 1657 | parseFree(field.field_type, @field(r, field.name), child_options); |
| 1654 | 1658 | } |
| 1655 | 1659 | fields_seen[i] = false; |
| 1656 | 1660 | } |
| 1657 | 1661 | } |
| 1658 | 1662 | if (field.is_comptime) { |
| 1659 | | if (!try parsesTo(field.field_type, field.default_value.?, tokens, options)) { |
| 1663 | if (!try parsesTo(field.field_type, field.default_value.?, tokens, child_options)) { |
| 1660 | 1664 | return error.UnexpectedValue; |
| 1661 | 1665 | } |
| 1662 | 1666 | } else { |
| 1663 | | @field(r, field.name) = try parse(field.field_type, tokens, options); |
| 1667 | @field(r, field.name) = try parse(field.field_type, tokens, child_options); |
| 1664 | 1668 | } |
| 1665 | 1669 | fields_seen[i] = true; |
| 1666 | 1670 | found = true; |
| ... | ... | @@ -1697,6 +1701,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1697 | 1701 | .ArrayBegin => { |
| 1698 | 1702 | var r: T = undefined; |
| 1699 | 1703 | var i: usize = 0; |
| 1704 | var child_options = options; |
| 1705 | child_options.allow_trailing_data = true; |
| 1700 | 1706 | errdefer { |
| 1701 | 1707 | // Without the r.len check `r[i]` is not allowed |
| 1702 | 1708 | if (r.len > 0) while (true) : (i -= 1) { |
| ... | ... | @@ -1705,7 +1711,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1705 | 1711 | }; |
| 1706 | 1712 | } |
| 1707 | 1713 | while (i < r.len) : (i += 1) { |
| 1708 | | r[i] = try parse(arrayInfo.child, tokens, options); |
| 1714 | r[i] = try parse(arrayInfo.child, tokens, child_options); |
| 1709 | 1715 | } |
| 1710 | 1716 | const tok = (try tokens.next()) orelse return error.UnexpectedEndOfJson; |
| 1711 | 1717 | switch (tok) { |
| ... | ... | @@ -1786,7 +1792,13 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1786 | 1792 | |
| 1787 | 1793 | pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) !T { |
| 1788 | 1794 | const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson; |
| 1789 | | return parseInternal(T, token, tokens, options); |
| 1795 | const r = try parseInternal(T, token, tokens, options); |
| 1796 | errdefer parseFree(T, r, options); |
| 1797 | if (!options.allow_trailing_data) { |
| 1798 | if ((try tokens.next()) != null) unreachable; |
| 1799 | assert(tokens.i >= tokens.slice.len); |
| 1800 | } |
| 1801 | return r; |
| 1790 | 1802 | } |
| 1791 | 1803 | |
| 1792 | 1804 | /// Releases resources created by `parse`. |
| ... | ... | @@ -1871,6 +1883,13 @@ test "parse into enum" { |
| 1871 | 1883 | try testing.expectError(error.InvalidEnumTag, parse(T, &TokenStream.init("\"Qux\""), ParseOptions{})); |
| 1872 | 1884 | } |
| 1873 | 1885 | |
| 1886 | test "parse with trailing data" { |
| 1887 | try testing.expectEqual(false, try parse(bool, &TokenStream.init("falsed"), ParseOptions{ .allow_trailing_data = true })); |
| 1888 | try testing.expectError(error.InvalidTopLevelTrailing, parse(bool, &TokenStream.init("falsed"), ParseOptions{ .allow_trailing_data = false })); |
| 1889 | // trailing whitespace is okay |
| 1890 | try testing.expectEqual(false, try parse(bool, &TokenStream.init("false \n"), ParseOptions{ .allow_trailing_data = false })); |
| 1891 | } |
| 1892 | |
| 1874 | 1893 | test "parse into that allocates a slice" { |
| 1875 | 1894 | try testing.expectError(error.AllocatorRequired, parse([]u8, &TokenStream.init("\"foo\""), ParseOptions{})); |
| 1876 | 1895 | |