| ... | @@ -1464,6 +1464,8 @@ pub const ParseOptions = struct { | ... | @@ -1464,6 +1464,8 @@ pub const ParseOptions = struct { |
| 1464 | | 1464 | |
| 1465 | /// If false, finding an unknown field returns an error. | 1465 | /// If false, finding an unknown field returns an error. |
| 1466 | ignore_unknown_fields: bool = false, | 1466 | ignore_unknown_fields: bool = false, |
| | 1467 | |
| | 1468 | allow_trailing_data: bool = false, |
| 1467 | }; | 1469 | }; |
| 1468 | | 1470 | |
| 1469 | fn skipValue(tokens: *TokenStream) !void { | 1471 | fn skipValue(tokens: *TokenStream) !void { |
| ... | @@ -1627,6 +1629,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: | ... | @@ -1627,6 +1629,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1627 | .ObjectEnd => break, | 1629 | .ObjectEnd => break, |
| 1628 | .String => |stringToken| { | 1630 | .String => |stringToken| { |
| 1629 | const key_source_slice = stringToken.slice(tokens.slice, tokens.i - 1); | 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 | var found = false; | 1634 | var found = false; |
| 1631 | inline for (structInfo.fields) |field, i| { | 1635 | inline for (structInfo.fields) |field, i| { |
| 1632 | // TODO: using switches here segfault the compiler (#2727?) | 1636 | // TODO: using switches here segfault the compiler (#2727?) |
| ... | @@ -1643,24 +1647,24 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: | ... | @@ -1643,24 +1647,24 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1643 | // } | 1647 | // } |
| 1644 | if (options.duplicate_field_behavior == .UseFirst) { | 1648 | if (options.duplicate_field_behavior == .UseFirst) { |
| 1645 | // unconditonally ignore value. for comptime fields, this skips check against default_value | 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 | found = true; | 1651 | found = true; |
| 1648 | break; | 1652 | break; |
| 1649 | } else if (options.duplicate_field_behavior == .Error) { | 1653 | } else if (options.duplicate_field_behavior == .Error) { |
| 1650 | return error.DuplicateJSONField; | 1654 | return error.DuplicateJSONField; |
| 1651 | } else if (options.duplicate_field_behavior == .UseLast) { | 1655 | } else if (options.duplicate_field_behavior == .UseLast) { |
| 1652 | if (!field.is_comptime) { | 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 | fields_seen[i] = false; | 1659 | fields_seen[i] = false; |
| 1656 | } | 1660 | } |
| 1657 | } | 1661 | } |
| 1658 | if (field.is_comptime) { | 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 | return error.UnexpectedValue; | 1664 | return error.UnexpectedValue; |
| 1661 | } | 1665 | } |
| 1662 | } else { | 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 | fields_seen[i] = true; | 1669 | fields_seen[i] = true; |
| 1666 | found = true; | 1670 | found = true; |
| ... | @@ -1697,6 +1701,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: | ... | @@ -1697,6 +1701,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1697 | .ArrayBegin => { | 1701 | .ArrayBegin => { |
| 1698 | var r: T = undefined; | 1702 | var r: T = undefined; |
| 1699 | var i: usize = 0; | 1703 | var i: usize = 0; |
| | 1704 | var child_options = options; |
| | 1705 | child_options.allow_trailing_data = true; |
| 1700 | errdefer { | 1706 | errdefer { |
| 1701 | // Without the r.len check `r[i]` is not allowed | 1707 | // Without the r.len check `r[i]` is not allowed |
| 1702 | if (r.len > 0) while (true) : (i -= 1) { | 1708 | if (r.len > 0) while (true) : (i -= 1) { |
| ... | @@ -1705,7 +1711,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: | ... | @@ -1705,7 +1711,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1705 | }; | 1711 | }; |
| 1706 | } | 1712 | } |
| 1707 | while (i < r.len) : (i += 1) { | 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 | const tok = (try tokens.next()) orelse return error.UnexpectedEndOfJson; | 1716 | const tok = (try tokens.next()) orelse return error.UnexpectedEndOfJson; |
| 1711 | switch (tok) { | 1717 | switch (tok) { |
| ... | @@ -1786,7 +1792,13 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: | ... | @@ -1786,7 +1792,13 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1786 | | 1792 | |
| 1787 | pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) !T { | 1793 | pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) !T { |
| 1788 | const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson; | 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 | /// Releases resources created by `parse`. | 1804 | /// Releases resources created by `parse`. |
| ... | @@ -1871,6 +1883,13 @@ test "parse into enum" { | ... | @@ -1871,6 +1883,13 @@ test "parse into enum" { |
| 1871 | try testing.expectError(error.InvalidEnumTag, parse(T, &TokenStream.init("\"Qux\""), ParseOptions{})); | 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 | test "parse into that allocates a slice" { | 1893 | test "parse into that allocates a slice" { |
| 1875 | try testing.expectError(error.AllocatorRequired, parse([]u8, &TokenStream.init("\"foo\""), ParseOptions{})); | 1894 | try testing.expectError(error.AllocatorRequired, parse([]u8, &TokenStream.init("\"foo\""), ParseOptions{})); |
| 1876 | | 1895 | |