authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-14 15:32:32-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-05-14 15:32:32-04:00
log114c6612cb0709a317f941b49daa4d4849dc24e1
tree9a531106e63f3caa7ee25d633ee8966a1a02cf92
parent8f35c60b39dad1ecb1609f6105f1e41a52df87ae
parent11fd2aa76770a0bef87a6ce304e1893b490bba83
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8764 from mattbork/json-usefirst

Fix duplicate_field_behavior UseFirst for parse in json.zig

1 files changed, 37 insertions(+), 9 deletions(-)

lib/std/json.zig+37-9
...@@ -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 // 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;
1576 break;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 }
...@@ -1731,7 +1736,9 @@ pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void {...@@ -1731,7 +1736,9 @@ pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void {
1731 },1736 },
1732 .Struct => |structInfo| {1737 .Struct => |structInfo| {
1733 inline for (structInfo.fields) |field| {1738 inline for (structInfo.fields) |field| {
1734 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 }
1735 }1742 }
1736 },1743 },
1737 .Array => |arrayInfo| {1744 .Array => |arrayInfo| {
...@@ -1908,14 +1915,19 @@ test "parse with comptime field" {...@@ -1908,14 +1915,19 @@ test "parse with comptime field" {
1908 },1915 },
1909 };1916 };
19101917
1911 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(
1912 \\{1923 \\{
1913 \\ "kind": "float",1924 \\ "kind": "float",
1914 \\ "b": 1.01925 \\ "b": 1.0
1915 \\}1926 \\}
1916 ), .{1927 ), options);
1917 .allocator = std.testing.allocator,1928
1918 });1929 // check that parseFree doesn't try to free comptime fields
1930 parseFree(T, r, options);
1919 }1931 }
1920}1932}
19211933
...@@ -2002,17 +2014,33 @@ test "parse into struct with duplicate field" {...@@ -2002,17 +2014,33 @@ test "parse into struct with duplicate field" {
2002 const ballast = try testing.allocator.alloc(u64, 1);2014 const ballast = try testing.allocator.alloc(u64, 1);
2003 defer testing.allocator.free(ballast);2015 defer testing.allocator.free(ballast);
20042016
2005 const options = ParseOptions{2017 const options_first = ParseOptions{
2018 .allocator = testing.allocator,
2019 .duplicate_field_behavior = .UseFirst
2020 };
2021
2022 const options_last = ParseOptions{
2006 .allocator = testing.allocator,2023 .allocator = testing.allocator,
2007 .duplicate_field_behavior = .UseLast,2024 .duplicate_field_behavior = .UseLast,
2008 };2025 };
2026
2009 const str = "{ \"a\": 1, \"a\": 0.25 }";2027 const str = "{ \"a\": 1, \"a\": 0.25 }";
20102028
2011 const T1 = struct { a: *u64 };2029 const T1 = struct { a: *u64 };
2012 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));
20132033
2014 const T2 = struct { a: f64 };2034 const T2 = struct { a: f64 };
2015 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));
2036 try testing.expectEqual(T2{ .a = 0.25 }, try parse(T2, &TokenStream.init(str), options_last));
2037
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));
2016}2044}
20172045
2018/// 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.