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 {...@@ -33,6 +33,9 @@ pub const StringifyOptions = struct {
3333
34 /// Should unicode characters be escaped in strings?34 /// Should unicode characters be escaped in strings?
35 escape_unicode: bool = false,35 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,
36};39};
3740
38/// Writes the given value to the `std.io.Writer` stream.41/// Writes the given value to the `std.io.Writer` stream.
...@@ -161,7 +164,7 @@ pub fn writeStreamArbitraryDepth(...@@ -161,7 +164,7 @@ pub fn writeStreamArbitraryDepth(
161/// * Zig `bool` -> JSON `true` or `false`.164/// * Zig `bool` -> JSON `true` or `false`.
162/// * Zig `?T` -> `null` or the rendering of `T`.165/// * Zig `?T` -> `null` or the rendering of `T`.
163/// * Zig `i32`, `u64`, etc. -> JSON number or string.166/// * 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.
165/// * Zig floats -> JSON number or string.168/// * Zig floats -> JSON number or string.
166/// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number.169/// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number.
167/// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00".170/// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00".
...@@ -399,21 +402,15 @@ pub fn WriteStream(...@@ -399,21 +402,15 @@ pub fn WriteStream(
399 pub fn write(self: *Self, value: anytype) Error!void {402 pub fn write(self: *Self, value: anytype) Error!void {
400 const T = @TypeOf(value);403 const T = @TypeOf(value);
401 switch (@typeInfo(T)) {404 switch (@typeInfo(T)) {
402 .Int => |info| {405 .Int => {
403 if (info.bits < 53) {406 try self.valueStart();
404 try self.valueStart();407 if (self.options.emit_nonportable_numbers_as_strings and
405 try self.stream.print("{}", .{value});408 (value <= -(1 << 53) or value >= (1 << 53)))
406 self.valueDone();409 {
407 return;410 try self.stream.print("\"{}\"", .{value});
408 }411 } else {
409 if (value < 4503599627370496 and (info.signedness == .unsigned or value > -4503599627370496)) {
410 try self.valueStart();
411 try self.stream.print("{}", .{value});412 try self.stream.print("{}", .{value});
412 self.valueDone();
413 return;
414 }413 }
415 try self.valueStart();
416 try self.stream.print("\"{}\"", .{value});
417 self.valueDone();414 self.valueDone();
418 return;415 return;
419 },416 },
lib/std/json/stringify_test.zig+6
...@@ -126,6 +126,7 @@ test "stringify basic types" {...@@ -126,6 +126,7 @@ test "stringify basic types" {
126 try testStringify("4.2e+01", 42.0, .{});126 try testStringify("4.2e+01", 42.0, .{});
127 try testStringify("42", @as(u8, 42), .{});127 try testStringify("42", @as(u8, 42), .{});
128 try testStringify("42", @as(u128, 42), .{});128 try testStringify("42", @as(u128, 42), .{});
129 try testStringify("9999999999999999", 9999999999999999, .{});
129 try testStringify("4.2e+01", @as(f32, 42), .{});130 try testStringify("4.2e+01", @as(f32, 42), .{});
130 try testStringify("4.2e+01", @as(f64, 42), .{});131 try testStringify("4.2e+01", @as(f64, 42), .{});
131 try testStringify("\"ItBroke\"", @as(anyerror, error.ItBroke), .{});132 try testStringify("\"ItBroke\"", @as(anyerror, error.ItBroke), .{});
...@@ -432,3 +433,8 @@ test "print" {...@@ -432,3 +433,8 @@ test "print" {
432 ;433 ;
433 try std.testing.expectEqualStrings(expected, result);434 try std.testing.expectEqualStrings(expected, result);
434}435}
436
437test "nonportable numbers" {
438 try testStringify("9999999999999999", 9999999999999999, .{});
439 try testStringify("\"9999999999999999\"", 9999999999999999, .{ .emit_nonportable_numbers_as_strings = true });
440}