Commit 81a172a506

Eugene-Dash <144539106+eugene-dash@users.noreply.github.com>
2024-07-26 02:55:06
Add `std.json.ParseOptions.parse_numbers` to preserve float precision (#20744)
1 parent ed847b8
Changed files (3)
lib/std/json/dynamic.zig
@@ -22,6 +22,7 @@ pub const Array = ArrayList(Value);
 /// Represents any JSON value, potentially containing other JSON values.
 /// A .float value may be an approximation of the original value.
 /// Arbitrary precision numbers can be represented by .number_string values.
+/// See also `std.json.ParseOptions.parse_numbers`.
 pub const Value = union(enum) {
     null,
     bool: bool,
@@ -97,7 +98,11 @@ pub const Value = union(enum) {
                     return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }, options) orelse continue;
                 },
                 .allocated_number => |slice| {
-                    return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue;
+                    if (options.parse_numbers) {
+                        return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue;
+                    } else {
+                        return try handleCompleteValue(&stack, allocator, source, Value{ .number_string = slice }, options) orelse continue;
+                    }
                 },
 
                 .null => return try handleCompleteValue(&stack, allocator, source, .null, options) orelse continue,
lib/std/json/dynamic_test.zig
@@ -149,6 +149,19 @@ test "integer after float has proper type" {
     try std.testing.expect(parsed.object.get("ints").?.array.items[0] == .integer);
 }
 
+test "ParseOptions.parse_numbers prevents parsing when false" {
+    var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
+    defer arena_allocator.deinit();
+    const parsed = try parseFromSliceLeaky(Value, arena_allocator.allocator(),
+        \\{
+        \\  "float": 3.14,
+        \\  "int": 3
+        \\}
+    , .{ .parse_numbers = false });
+    try std.testing.expect(parsed.object.get("float").? == .number_string);
+    try std.testing.expect(parsed.object.get("int").? == .number_string);
+}
+
 test "escaped characters" {
     var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
     defer arena_allocator.deinit();
lib/std/json/static.zig
@@ -42,6 +42,15 @@ pub const ParseOptions = struct {
     /// The default with a `*std.json.Reader` input is `.alloc_always`.
     /// Ignored for `parseFromValue` and `parseFromValueLeaky`.
     allocate: ?AllocWhen = null,
+
+    /// When parsing to a `std.json.Value`, set this option to false to always emit
+    /// JSON numbers as unparsed `std.json.Value.number_string`.
+    /// Otherwise, JSON numbers are parsed as either `std.json.Value.integer`,
+    /// `std.json.Value.float` or left as unparsed `std.json.Value.number_string`
+    /// depending on the format and value of the JSON number.
+    /// When this option is true, JSON numbers encoded as floats (see `std.json.isNumberFormattedLikeAnInteger`)
+    /// may lose precision when being parsed into `std.json.Value.float`.
+    parse_numbers: bool = true,
 };
 
 pub fn Parsed(comptime T: type) type {