From d5609bed29c463b991584ed0499dd158d65e8bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 10 Feb 2025 11:09:17 +0100 Subject: [PATCH] Use JSON::Coder when available --- .../lib/active_support/json/encoding.rb | 46 ++++++++++++++++++- activesupport/test/json/encoding_test.rb | 13 ++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 2fe140270d54..1fafaac44346 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -97,6 +97,45 @@ def stringify(jsonified) end end + if defined?(::JSON::Coder) + class JSONGemCoderEncoder # :nodoc: + JSON_NATIVE_TYPES = [Hash, Array, Float, String, Symbol, Integer, NilClass, TrueClass, FalseClass].freeze + CODER = ::JSON::Coder.new do |value| + json_value = value.as_json + # Handle objects returning self from as_json + if json_value.equal?(value) + next ::JSON::Fragment.new(value.to_json) + end + # Handle objects not returning JSON-native types from as_json + until JSON_NATIVE_TYPES.include?(json_value.class) + json_value = json_value.as_json + end + json_value + end + + + def initialize(options = nil) + @options = options ? options.dup.freeze : {}.freeze + end + + # Encode the given object into a JSON string + def encode(value) + value = value.as_json(@options) unless @options.empty? + + json = CODER.dump(value) + + if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json) + json.gsub!(">", '\u003e') + json.gsub!("<", '\u003c') + json.gsub!("&", '\u0026') + end + json.gsub!("\u2028", '\u2028') + json.gsub!("\u2029", '\u2029') + json + end + end + end + class << self # If true, use ISO 8601 format for dates and times. Otherwise, fall back # to the Active Support legacy format. @@ -126,7 +165,12 @@ def encode_without_options(value) # :nodoc: self.use_standard_json_time_format = true self.escape_html_entities_in_json = true - self.json_encoder = JSONGemEncoder + self.json_encoder = + if defined?(::JSON::Coder) + JSONGemCoderEncoder + else + JSONGemEncoder + end self.time_precision = 3 end end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 6bffa7ff2275..d1938e28696b 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -504,3 +504,16 @@ def with_time_precision(value) ActiveSupport::JSON::Encoding.time_precision = old_value end end + +if defined?(::JSON::Coder) + class OldJSONEncodingTest < TestJSONEncoding + setup do + @json_encoder = ActiveSupport::JSON::Encoding.json_encoder + ActiveSupport::JSON::Encoding.json_encoder = ActiveSupport::JSON::Encoding::JSONGemEncoder + end + + teardown do + ActiveSupport::JSON::Encoding.json_encoder = @json_encoder + end + end +end