Skip to content

Commit 81a172a

Browse files
authored
Add std.json.ParseOptions.parse_numbers to preserve float precision (#20744)
1 parent ed847b8 commit 81a172a

File tree

3 files changed

+28
-1
lines changed

3 files changed

+28
-1
lines changed

lib/std/json/dynamic.zig

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub const Array = ArrayList(Value);
2222
/// Represents any JSON value, potentially containing other JSON values.
2323
/// A .float value may be an approximation of the original value.
2424
/// Arbitrary precision numbers can be represented by .number_string values.
25+
/// See also `std.json.ParseOptions.parse_numbers`.
2526
pub const Value = union(enum) {
2627
null,
2728
bool: bool,
@@ -97,7 +98,11 @@ pub const Value = union(enum) {
9798
return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }, options) orelse continue;
9899
},
99100
.allocated_number => |slice| {
100-
return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue;
101+
if (options.parse_numbers) {
102+
return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue;
103+
} else {
104+
return try handleCompleteValue(&stack, allocator, source, Value{ .number_string = slice }, options) orelse continue;
105+
}
101106
},
102107

103108
.null => return try handleCompleteValue(&stack, allocator, source, .null, options) orelse continue,

lib/std/json/dynamic_test.zig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,19 @@ test "integer after float has proper type" {
149149
try std.testing.expect(parsed.object.get("ints").?.array.items[0] == .integer);
150150
}
151151

152+
test "ParseOptions.parse_numbers prevents parsing when false" {
153+
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
154+
defer arena_allocator.deinit();
155+
const parsed = try parseFromSliceLeaky(Value, arena_allocator.allocator(),
156+
\\{
157+
\\ "float": 3.14,
158+
\\ "int": 3
159+
\\}
160+
, .{ .parse_numbers = false });
161+
try std.testing.expect(parsed.object.get("float").? == .number_string);
162+
try std.testing.expect(parsed.object.get("int").? == .number_string);
163+
}
164+
152165
test "escaped characters" {
153166
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
154167
defer arena_allocator.deinit();

lib/std/json/static.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ pub const ParseOptions = struct {
4242
/// The default with a `*std.json.Reader` input is `.alloc_always`.
4343
/// Ignored for `parseFromValue` and `parseFromValueLeaky`.
4444
allocate: ?AllocWhen = null,
45+
46+
/// When parsing to a `std.json.Value`, set this option to false to always emit
47+
/// JSON numbers as unparsed `std.json.Value.number_string`.
48+
/// Otherwise, JSON numbers are parsed as either `std.json.Value.integer`,
49+
/// `std.json.Value.float` or left as unparsed `std.json.Value.number_string`
50+
/// depending on the format and value of the JSON number.
51+
/// When this option is true, JSON numbers encoded as floats (see `std.json.isNumberFormattedLikeAnInteger`)
52+
/// may lose precision when being parsed into `std.json.Value.float`.
53+
parse_numbers: bool = true,
4554
};
4655

4756
pub fn Parsed(comptime T: type) type {

0 commit comments

Comments
 (0)