[ruby/json] Canonicalize integers as doubles in RFC 8785 mode

https://github.com/ruby/json/commit/3d9d6dd32d
This commit is contained in:
ydah authored and git committed 2026-10-08 07:05:47 +00:00
1 parent 54cc01ede0
commit 87c567e721
4 files changed
+66 -3

No files matched your search

+16 -3
View File
@@ -1135,13 +1135,28 @@ static void generate_json_true(FBuffer *buffer, struct generate_json_data *data,
fbuffer_append(buffer, "true", 4);
}
static void generate_json_rfc8785_number(FBuffer *buffer, VALUE obj)
{
VALUE str = rb_proc_call_with_block(rfc8785_number_formatter_proc, 1, &obj, Qnil);
Check_Type(str, T_STRING);
fbuffer_append_str(buffer, str);
}
static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
{
if (RB_UNLIKELY(data->state->rfc8785)) {
generate_json_rfc8785_number(buffer, obj);
return;
}
fbuffer_append_long(buffer, FIX2LONG(obj));
}
static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
{
if (RB_UNLIKELY(data->state->rfc8785)) {
generate_json_rfc8785_number(buffer, obj);
return;
}
VALUE tmp = rb_funcall(obj, i_to_s, 0);
fbuffer_append_str(buffer, StringValue(tmp));
}
@@ -1171,9 +1186,7 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
}
if (RB_UNLIKELY(data->state->rfc8785)) {
VALUE str = rb_proc_call_with_block(rfc8785_number_formatter_proc, 1, &obj, Qnil);
Check_Type(str, T_STRING);
fbuffer_append_str(buffer, str);
generate_json_rfc8785_number(buffer, obj);
return;
}
+2
View File
@@ -416,6 +416,8 @@ require 'json/common'
# order, allowing for arbitrary sort orders.
# - Option +rfc8785+ (boolean) controls whether the generated JSON will be canonicalized
# as defined in RFC8785.
# Integers are converted to Float before formatting and may be rounded.
# Integers outside the finite Float range raise JSON::GeneratorError.
#
# In this example, +obj+ is used first to generate the shortest
# \JSON data (no whitespace), then again with all formatting options
+6
View File
@@ -60,6 +60,12 @@ module JSON
# Directly lifted from Gregg Kellogg's json-canonicalization
generator::State.rfc8785_number_formatter_proc = shareable_lambda(->(num) {
if Integer === num
if num > Float::MAX || num < -Float::MAX
raise GeneratorError.new("Integer out of range for RFC 8785", num)
end
num = num.to_f
end
if num.zero?
"0"
else
+42
View File
@@ -1283,6 +1283,48 @@ class JSONGeneratorTest < Test::Unit::TestCase
$VERBOSE = verbose
end
{
0 => '0',
1 => '1',
2**53 - 1 => '9007199254740991',
2**53 => '9007199254740992',
2**53 + 1 => '9007199254740992',
2**53 + 3 => '9007199254740996',
2**63 - 1 => '9223372036854776000',
2**68 => '295147905179352830000',
10**20 => '100000000000000000000',
10**21 => '1e+21',
Float::MAX.to_i => '1.7976931348623157e+308',
}.each do |integer, expected|
define_method("test_rfc8785_integer_#{integer}") do
[1, -1].each do |sign|
number = sign * integer
json = sign < 0 && integer != 0 ? "-#{expected}" : expected
assert_rfc8785 json, number
assert_rfc8785 %({"n":[#{json}]}), { 'n' => [number] }
assert_equal json, number.to_json(rfc8785: true)
assert_equal json, JSON.generate(number, rfc8785: true, max_nesting: false)
assert_equal json, JSON::Coder.new(rfc8785: true).dump(number)
io = StringIO.new
JSON.dump(number, io, rfc8785: true)
assert_equal json, io.string
assert_equal number.to_s, JSON.generate(number)
end
end
end
def test_rfc8785_rejects_out_of_range_integers
[Float::MAX.to_i + 1, 10**400].each do |integer|
[integer, -integer].each do |number|
error = assert_raise(JSON::GeneratorError) { JSON.generate(number, rfc8785: true) }
assert_same number, error.invalid_object
assert_equal 'Integer out of range for RFC 8785', error.message
assert_raise(JSON::GeneratorError) { JSON.dump([number], rfc8785: true) }
assert_equal number.to_s, JSON.generate(number)
end
end
end
def test_rfc8785_numbers
assert_rfc8785 '-9007199254740992', -9007199254740992
assert_rfc8785 '0', 0