mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
[ruby/json] Canonicalize integers as doubles in RFC 8785 mode
https://github.com/ruby/json/commit/3d9d6dd32d
This commit is contained in:
1 parent
54cc01ede0
commit
87c567e721
4 files changed
+66
-3
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user