Skip to content

Commit

Permalink
std.json: Parse -0 as a float instead of an integer (#17729)
Browse files Browse the repository at this point in the history
This is consistent with `JSON.parse("-0")` in JavaScript, RFC 8259
doesn't specifically mention what to do in this case.
If a negative zero is encoded the intention is likely to preserve the
sign.
  • Loading branch information
linusg authored Oct 27, 2023
1 parent a4cffd8 commit 772636e
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 1 deletion.
11 changes: 11 additions & 0 deletions lib/std/json/dynamic_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,17 @@ test "many object keys" {
try testing.expectEqualStrings("v5", parsed.value.object.get("k5").?.string);
}

test "negative zero" {
const doc = "-0";
var fbs = std.io.fixedBufferStream(doc);
var reader = smallBufferJsonReader(testing.allocator, fbs.reader());
defer reader.deinit();
var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{});
defer parsed.deinit();

try testing.expect(parsed.value.float == 0 and std.math.signbit(parsed.value.float));
}

fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) JsonReader(16, @TypeOf(io_reader)) {
return JsonReader(16, @TypeOf(io_reader)).init(allocator, io_reader);
}
3 changes: 2 additions & 1 deletion lib/std/json/scanner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1700,11 +1700,12 @@ fn appendSlice(list: *std.ArrayList(u8), buf: []const u8, max_value_len: usize)
}

/// For the slice you get from a `Token.number` or `Token.allocated_number`,
/// this function returns true if the number doesn't contain any fraction or exponent components.
/// this function returns true if the number doesn't contain any fraction or exponent components, and is not `-0`.
/// Note, the numeric value encoded by the value may still be an integer, such as `1.0`.
/// This function is meant to give a hint about whether integer parsing or float parsing should be used on the value.
/// This function will not give meaningful results on non-numeric input.
pub fn isNumberFormattedLikeAnInteger(value: []const u8) bool {
if (std.mem.eql(u8, value, "-0")) return false;
return std.mem.indexOfAny(u8, value, ".eE") == null;
}

Expand Down
13 changes: 13 additions & 0 deletions lib/std/json/scanner_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const TokenType = @import("./scanner.zig").TokenType;
const Diagnostics = @import("./scanner.zig").Diagnostics;
const Error = @import("./scanner.zig").Error;
const validate = @import("./scanner.zig").validate;
const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;

const example_document_str =
\\{
Expand Down Expand Up @@ -465,3 +466,15 @@ test "enableDiagnostics" {
try testDiagnostics(error.SyntaxError, 1, s.len, s.len - 1, s);
}
}

test isNumberFormattedLikeAnInteger {
try std.testing.expect(isNumberFormattedLikeAnInteger("0"));
try std.testing.expect(isNumberFormattedLikeAnInteger("1"));
try std.testing.expect(isNumberFormattedLikeAnInteger("123"));
try std.testing.expect(!isNumberFormattedLikeAnInteger("-0"));
try std.testing.expect(!isNumberFormattedLikeAnInteger("0.0"));
try std.testing.expect(!isNumberFormattedLikeAnInteger("1.0"));
try std.testing.expect(!isNumberFormattedLikeAnInteger("1.23"));
try std.testing.expect(!isNumberFormattedLikeAnInteger("1e10"));
try std.testing.expect(!isNumberFormattedLikeAnInteger("1E10"));
}

0 comments on commit 772636e

Please sign in to comment.