authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-08-08 12:08:21-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-29 14:26:20-07:00
logbaf5b4bc86635dcb14725c578f058f4c4dc27b90
treeda304cbd7d399316471623c24de675f6ecdfbe90
parenteaf9ac77d6f880b2d651e91b4def7f843c451866

Merge pull request #16707 from marler8997/jsonStringifyBigNumbers

std.json: fix roundtrip stringify for large integers

2 files changed, 17 insertions(+), 14 deletions(-)

lib/std/json/stringify.zig+11-14
......@@ -33,6 +33,9 @@ pub const StringifyOptions = struct {
3333
3434 /// Should unicode characters be escaped in strings?
3535 escape_unicode: bool = false,
36
37 /// When true, renders numbers outside the range `+-1<<53` (the precise integer range of f64) as JSON strings in base 10.
38 emit_nonportable_numbers_as_strings: bool = false,
3639};
3740
3841/// Writes the given value to the `std.io.Writer` stream.
......@@ -161,7 +164,7 @@ pub fn writeStreamArbitraryDepth(
161164/// * Zig `bool` -> JSON `true` or `false`.
162165/// * Zig `?T` -> `null` or the rendering of `T`.
163166/// * Zig `i32`, `u64`, etc. -> JSON number or string.
164/// * If the value is outside the range `±1<<53` (the precise integer rage of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number.
167/// * When option `emit_nonportable_numbers_as_strings` is true, if the value is outside the range `+-1<<53` (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number.
165168/// * Zig floats -> JSON number or string.
166169/// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number.
167170/// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00".
......@@ -399,21 +402,15 @@ pub fn WriteStream(
399402 pub fn write(self: *Self, value: anytype) Error!void {
400403 const T = @TypeOf(value);
401404 switch (@typeInfo(T)) {
402 .Int => |info| {
403 if (info.bits < 53) {
404 try self.valueStart();
405 try self.stream.print("{}", .{value});
406 self.valueDone();
407 return;
408 }
409 if (value < 4503599627370496 and (info.signedness == .unsigned or value > -4503599627370496)) {
410 try self.valueStart();
405 .Int => {
406 try self.valueStart();
407 if (self.options.emit_nonportable_numbers_as_strings and
408 (value <= -(1 << 53) or value >= (1 << 53)))
409 {
410 try self.stream.print("\"{}\"", .{value});
411 } else {
411412 try self.stream.print("{}", .{value});
412 self.valueDone();
413 return;
414413 }
415 try self.valueStart();
416 try self.stream.print("\"{}\"", .{value});
417414 self.valueDone();
418415 return;
419416 },
lib/std/json/stringify_test.zig+6
......@@ -126,6 +126,7 @@ test "stringify basic types" {
126126 try testStringify("4.2e+01", 42.0, .{});
127127 try testStringify("42", @as(u8, 42), .{});
128128 try testStringify("42", @as(u128, 42), .{});
129 try testStringify("9999999999999999", 9999999999999999, .{});
129130 try testStringify("4.2e+01", @as(f32, 42), .{});
130131 try testStringify("4.2e+01", @as(f64, 42), .{});
131132 try testStringify("\"ItBroke\"", @as(anyerror, error.ItBroke), .{});
......@@ -432,3 +433,8 @@ test "print" {
432433 ;
433434 try std.testing.expectEqualStrings(expected, result);
434435}
436
437test "nonportable numbers" {
438 try testStringify("9999999999999999", 9999999999999999, .{});
439 try testStringify("\"9999999999999999\"", 9999999999999999, .{ .emit_nonportable_numbers_as_strings = true });
440}