| ... | @@ -1573,11 +1573,16 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: | ... | @@ -1573,11 +1573,16 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1573 | // .UseLast => {}, | 1573 | // .UseLast => {}, |
| 1574 | // } | 1574 | // } |
| 1575 | if (options.duplicate_field_behavior == .UseFirst) { | 1575 | if (options.duplicate_field_behavior == .UseFirst) { |
| 1576 | // do nothing, check of fields_seen[i] below will parse value without overwriting field | 1576 | // unconditonally ignore value. for comptime fields, this skips check against default_value |
| | 1577 | parseFree(field.field_type, try parse(field.field_type, tokens, options), options); |
| | 1578 | found = true; |
| | 1579 | break; |
| 1577 | } else if (options.duplicate_field_behavior == .Error) { | 1580 | } else if (options.duplicate_field_behavior == .Error) { |
| 1578 | return error.DuplicateJSONField; | 1581 | return error.DuplicateJSONField; |
| 1579 | } else if (options.duplicate_field_behavior == .UseLast) { | 1582 | } else if (options.duplicate_field_behavior == .UseLast) { |
| 1580 | parseFree(field.field_type, @field(r, field.name), options); | 1583 | if (!field.is_comptime) { |
| | 1584 | parseFree(field.field_type, @field(r, field.name), options); |
| | 1585 | } |
| 1581 | fields_seen[i] = false; | 1586 | fields_seen[i] = false; |
| 1582 | } | 1587 | } |
| 1583 | } | 1588 | } |
| ... | @@ -1586,11 +1591,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: | ... | @@ -1586,11 +1591,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1586 | return error.UnexpectedValue; | 1591 | return error.UnexpectedValue; |
| 1587 | } | 1592 | } |
| 1588 | } else { | 1593 | } else { |
| 1589 | if (fields_seen[i]) { | 1594 | @field(r, field.name) = try parse(field.field_type, tokens, options); |
| 1590 | parseFree(field.field_type, try parse(field.field_type, tokens, options), options); | | |
| 1591 | } else { | | |
| 1592 | @field(r, field.name) = try parse(field.field_type, tokens, options); | | |
| 1593 | } | | |
| 1594 | } | 1595 | } |
| 1595 | fields_seen[i] = true; | 1596 | fields_seen[i] = true; |
| 1596 | found = true; | 1597 | found = true; |
| ... | @@ -1735,7 +1736,9 @@ pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void { | ... | @@ -1735,7 +1736,9 @@ pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void { |
| 1735 | }, | 1736 | }, |
| 1736 | .Struct => |structInfo| { | 1737 | .Struct => |structInfo| { |
| 1737 | inline for (structInfo.fields) |field| { | 1738 | inline for (structInfo.fields) |field| { |
| 1738 | parseFree(field.field_type, @field(value, field.name), options); | 1739 | if (!field.is_comptime) { |
| | 1740 | parseFree(field.field_type, @field(value, field.name), options); |
| | 1741 | } |
| 1739 | } | 1742 | } |
| 1740 | }, | 1743 | }, |
| 1741 | .Array => |arrayInfo| { | 1744 | .Array => |arrayInfo| { |
| ... | @@ -1912,14 +1915,19 @@ test "parse with comptime field" { | ... | @@ -1912,14 +1915,19 @@ test "parse with comptime field" { |
| 1912 | }, | 1915 | }, |
| 1913 | }; | 1916 | }; |
| 1914 | | 1917 | |
| 1915 | const r = try std.json.parse(T, &std.json.TokenStream.init( | 1918 | const options = ParseOptions{ |
| | 1919 | .allocator = std.testing.allocator, |
| | 1920 | }; |
| | 1921 | |
| | 1922 | const r = try parse(T, &TokenStream.init( |
| 1916 | \\{ | 1923 | \\{ |
| 1917 | \\ "kind": "float", | 1924 | \\ "kind": "float", |
| 1918 | \\ "b": 1.0 | 1925 | \\ "b": 1.0 |
| 1919 | \\} | 1926 | \\} |
| 1920 | ), .{ | 1927 | ), options); |
| 1921 | .allocator = std.testing.allocator, | 1928 | |
| 1922 | }); | 1929 | // check that parseFree doesn't try to free comptime fields |
| | 1930 | parseFree(T, r, options); |
| 1923 | } | 1931 | } |
| 1924 | } | 1932 | } |
| 1925 | | 1933 | |
| ... | @@ -2006,20 +2014,33 @@ test "parse into struct with duplicate field" { | ... | @@ -2006,20 +2014,33 @@ test "parse into struct with duplicate field" { |
| 2006 | const ballast = try testing.allocator.alloc(u64, 1); | 2014 | const ballast = try testing.allocator.alloc(u64, 1); |
| 2007 | defer testing.allocator.free(ballast); | 2015 | defer testing.allocator.free(ballast); |
| 2008 | | 2016 | |
| 2009 | const options = ParseOptions{ | 2017 | const options_first = ParseOptions{ |
| | 2018 | .allocator = testing.allocator, |
| | 2019 | .duplicate_field_behavior = .UseFirst |
| | 2020 | }; |
| | 2021 | |
| | 2022 | const options_last = ParseOptions{ |
| 2010 | .allocator = testing.allocator, | 2023 | .allocator = testing.allocator, |
| 2011 | .duplicate_field_behavior = .UseLast, | 2024 | .duplicate_field_behavior = .UseLast, |
| 2012 | }; | 2025 | }; |
| | 2026 | |
| 2013 | const str = "{ \"a\": 1, \"a\": 0.25 }"; | 2027 | const str = "{ \"a\": 1, \"a\": 0.25 }"; |
| 2014 | | 2028 | |
| 2015 | const T1 = struct { a: *u64 }; | 2029 | const T1 = struct { a: *u64 }; |
| 2016 | try testing.expectError(error.UnexpectedToken, parse(T1, &TokenStream.init(str), options)); | 2030 | // both .UseFirst and .UseLast should fail because second "a" value isn't a u64 |
| | 2031 | try testing.expectError(error.UnexpectedToken, parse(T1, &TokenStream.init(str), options_first)); |
| | 2032 | try testing.expectError(error.UnexpectedToken, parse(T1, &TokenStream.init(str), options_last)); |
| 2017 | | 2033 | |
| 2018 | const T2 = struct { a: f64 }; | 2034 | const T2 = struct { a: f64 }; |
| 2019 | try testing.expectEqual(T2{ .a = 0.25 }, try parse(T2, &TokenStream.init(str), options)); | 2035 | try testing.expectEqual(T2{ .a = 1.0 }, try parse(T2, &TokenStream.init(str), options_first)); |
| 2020 | try testing.expectEqual(T2{ .a = 1.0 }, try parse(T2, &TokenStream.init(str), | 2036 | try testing.expectEqual(T2{ .a = 0.25 }, try parse(T2, &TokenStream.init(str), options_last)); |
| 2021 | .{ .duplicate_field_behavior = .UseFirst } | 2037 | |
| 2022 | )); | 2038 | const T3 = struct { comptime a: f64 = 1.0 }; |
| | 2039 | // .UseFirst should succeed because second "a" value is unconditionally ignored (even though != 1.0) |
| | 2040 | const t3 = T3{ .a = 1.0 }; |
| | 2041 | try testing.expectEqual(t3, try parse(T3, &TokenStream.init(str), options_first)); |
| | 2042 | // .UseLast should fail because second "a" value is 0.25 which is not equal to default value of 1.0 |
| | 2043 | try testing.expectError(error.UnexpectedValue, parse(T3, &TokenStream.init(str), options_last)); |
| 2023 | } | 2044 | } |
| 2024 | | 2045 | |
| 2025 | /// A non-stream JSON parser which constructs a tree of Value's. | 2046 | /// A non-stream JSON parser which constructs a tree of Value's. |