From 647d82b0c876a4d4242afb8a5868c690fc9385d4 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 13 Oct 2026 09:15:24 +0200 Subject: [PATCH] Refactor `rfc8785_sort_keys_proc` --- lib/json/ext.rb | 86 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/lib/json/ext.rb b/lib/json/ext.rb index 7d7b09b27..75d0e4e3d 100644 --- a/lib/json/ext.rb +++ b/lib/json/ext.rb @@ -38,6 +38,56 @@ def shareable_lambda(block) # :nodoc: block end end + + def valid_encoding?(str) # :nodoc: + str.ascii_only? || (str.encoding == Encoding::UTF_8 && str.valid_encoding?) + end + + def coerce_key(key, as_json) # :nodoc: + as_json_called = false + + unless String === key || Symbol === key + key = as_json.call(key, true) + as_json_called = true + end + + if Symbol === key + key = key.to_s + end + + unless String === key + raise GeneratorError.new("#{key.class} not allowed as object key in JSON", key) + end + + unless valid_encoding?(key) + if as_json_called + raise GeneratorError.new("source sequence is illegal/malformed utf-8", key) + end + + key = as_json.call(key, true) + unless String === key + raise GeneratorError.new("#{key.class} not allowed as object key in JSON", key) + end + + unless Ext.valid_encoding?(key) + raise GeneratorError.new("source sequence is illegal/malformed utf-8", key) + end + end + + key + end + + def on_duplicate_key(hash, as_json) + set = {} + hash.each_key do |key| + key = coerce_key(key, as_json) + if set[key] + raise GeneratorError.new("detected duplicate key #{key.inspect}", key) + else + set[key] = true + end + end + end end require 'json/ext/parser' @@ -101,33 +151,17 @@ def shareable_lambda(block) # :nodoc: }) generator::State.rfc8785_sort_keys_proc = shareable_lambda(->(hash, as_json) { - if as_json - converted = {} - hash.each do |key, value| - string = (String === key || Symbol === key) ? key.to_s : nil - unless string && (string.ascii_only? || (string.encoding == Encoding::UTF_8 && string.valid_encoding?)) - key = as_json.call(key, true) - unless String === key || Symbol === key - raise GeneratorError.new("#{key.class} not allowed as object key in JSON", key) - end - begin - string = key.to_s.encode(Encoding::UTF_8) - rescue EncodingError => error - raise GeneratorError.new(error.message, key) - end - unless string.valid_encoding? - raise GeneratorError.new("source sequence is illegal/malformed utf-8", key) - end - key = string - end - if converted.key?(key) - raise GeneratorError.new("detected duplicate key #{key.inspect}", key) - end - converted[key] = value - end - hash = converted + converted = if as_json + hash.transform_keys { |k| Ext.coerce_key(k, as_json) } + else + hash.transform_keys(&:to_s) end - hash.sort_by { |k,| k.to_s.encode(Encoding::UTF_16) }.to_h + + if converted.size < hash.size + Ext.on_duplicate_key(hash, as_json) + end + + converted.sort_by { |k,| k.encode(Encoding::UTF_16) }.to_h }) JSON.generator = generator