From 787a0c11ec98ddebb3bcba8b19533aefd34a23ef Mon Sep 17 00:00:00 2001 From: ydah Date: Wed, 7 Oct 2026 22:50:43 +0900 Subject: [PATCH] Sort RFC 8785 object properties by their keys --- lib/json/ext.rb | 2 +- test/json/json_generator_test.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/json/ext.rb b/lib/json/ext.rb index 1c7965d0..9c7e090b 100644 --- a/lib/json/ext.rb +++ b/lib/json/ext.rb @@ -95,7 +95,7 @@ def shareable_lambda(block) # :nodoc: }) generator::State.rfc8785_sort_keys_proc = shareable_lambda(->(hash) { - hash.sort_by { |k| k.to_s.encode(Encoding::UTF_16) }.to_h + hash.sort_by { |k,| k.to_s.encode(Encoding::UTF_16) }.to_h }) JSON.generator = generator diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 75aa39de..02cff38c 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -1253,6 +1253,36 @@ def test_large_depth_raises end end + def test_rfc8785_key_order + assert_rfc8785 '{"a":1,"a b":2}', { 'a' => 1, 'a b' => 2 } + assert_rfc8785 '{"a":2,"a!":1}', { 'a!' => 1, 'a' => 2 } + assert_rfc8785 '{"\n":1,"A":2}', { "\n" => 1, 'A' => 2 } + assert_rfc8785 '[{"a":1,"a b":2}]', [{ 'a b' => 2, 'a' => 1 }] + end + + def test_rfc8785_mixed_key_order + assert_rfc8785 '{"10":2,"9":1}', { '9' => 1, 10 => 2 } + assert_rfc8785 '{"a":1,"b":2}', { a: 1, 'b' => 2 } + end + + def test_rfc8785_key_order_with_default_external_encoding + # https://www.rfc-editor.org/rfc/rfc8785.html#section-3.2.3 + hash = { "\u20ac" => 1, "\r" => 2, "\ufb33" => 3, '1' => 4, + "\u{1f600}" => 5, "\u0080" => 6, "\u00f6" => 7 } + expected = "{\"\\r\":2,\"1\":4,\"\u0080\":6,\"\u00f6\":7,\"\u20ac\":1,\"\u{1f600}\":5,\"\ufb33\":3}" + verbose = $VERBOSE + previous_encoding = Encoding.default_external + $VERBOSE = nil + outputs = [Encoding::UTF_8, Encoding::US_ASCII].map do |encoding| + Encoding.default_external = encoding + JSON.generate(hash, rfc8785: true) + end + assert_equal [expected, expected], outputs + ensure + Encoding.default_external = previous_encoding + $VERBOSE = verbose + end + def test_rfc8785_numbers assert_rfc8785 '-9007199254740992', -9007199254740992 assert_rfc8785 '0', 0