From 81a172a5064559d1f968f240204f8ce545852441 Mon Sep 17 00:00:00 2001 From: Eugene-Dash <144539106+eugene-dash@users.noreply.github.com> Date: Thu, 25 Jul 2024 20:55:06 -0400 Subject: [PATCH] Add `std.json.ParseOptions.parse_numbers` to preserve float precision (#20744) --- lib/std/json/dynamic.zig | 7 ++++++- lib/std/json/dynamic_test.zig | 13 +++++++++++++ lib/std/json/static.zig | 9 +++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/std/json/dynamic.zig b/lib/std/json/dynamic.zig index 7bacbd60d1997de6c196fdd2faa5aa26d3274925..dae9ba836d9eeca389ab62eaf0b2666728e93e21 100644 --- a/lib/std/json/dynamic.zig +++ b/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, diff --git a/lib/std/json/dynamic_test.zig b/lib/std/json/dynamic_test.zig index 9e181dea9c39228f877a375e79d96f8a5c42542f..45cdc0d0c7c093bcb7ff47c5d40558399c07cafc 100644 --- a/lib/std/json/dynamic_test.zig +++ b/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(); diff --git a/lib/std/json/static.zig b/lib/std/json/static.zig index ea0bb6c0f2c12ee42eef33e9e60113eb41b648e7..74901f85a885e02e891d4f92e073cc88f99d57fe 100644 --- a/lib/std/json/static.zig +++ b/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 { -- 2.54.0