Skip to content

Commit af38db4

Browse files
etiennebarriebyroot
andcommitted
Use JSON::Coder when available
Co-authored-by: Jean Boussier <[email protected]>
1 parent 424b9b5 commit af38db4

File tree

4 files changed

+96
-2
lines changed

4 files changed

+96
-2
lines changed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ GEM
313313
jmespath (1.6.2)
314314
jsbundling-rails (1.3.1)
315315
railties (>= 6.0.0)
316-
json (2.9.1)
316+
json (2.10.0)
317317
jwt (2.10.1)
318318
base64
319319
kamal (2.4.0)

activesupport/lib/active_support/core_ext/string/output_safety.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ def to_s
139139
self
140140
end
141141

142+
def as_json
143+
to_str
144+
end
145+
142146
def to_param
143147
to_str
144148
end

activesupport/lib/active_support/json/encoding.rb

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,51 @@ def stringify(jsonified)
9797
end
9898
end
9999

100+
if defined?(::JSON::Coder)
101+
class JSONGemCoderEncoder # :nodoc:
102+
JSON_NATIVE_TYPES = [Hash, Array, Float, String, Symbol, Integer, NilClass, TrueClass, FalseClass].freeze
103+
CODER = ::JSON::Coder.new do |value|
104+
json_value = value.as_json
105+
# Handle objects returning self from as_json
106+
if json_value.equal?(value)
107+
next ::JSON::Fragment.new(::JSON.generate(json_value))
108+
end
109+
# Handle objects not returning JSON-native types from as_json
110+
count = 5
111+
until JSON_NATIVE_TYPES.include?(json_value.class)
112+
raise SystemStackError if count == 0
113+
json_value = json_value.as_json
114+
count -= 1
115+
end
116+
json_value
117+
end
118+
119+
120+
def initialize(options = nil)
121+
@options = options ? options.dup.freeze : {}.freeze
122+
end
123+
124+
# Encode the given object into a JSON string
125+
def encode(value)
126+
value = value.as_json(@options) unless @options.empty?
127+
128+
json = CODER.dump(value)
129+
130+
# Rails does more escaping than the JSON gem natively does (we
131+
# escape \u2028 and \u2029 and optionally >, <, & to work around
132+
# certain browser problems).
133+
if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
134+
json.gsub!(">", '\u003e')
135+
json.gsub!("<", '\u003c')
136+
json.gsub!("&", '\u0026')
137+
end
138+
json.gsub!("\u2028", '\u2028')
139+
json.gsub!("\u2029", '\u2029')
140+
json
141+
end
142+
end
143+
end
144+
100145
class << self
101146
# If true, use ISO 8601 format for dates and times. Otherwise, fall back
102147
# to the Active Support legacy format.
@@ -126,7 +171,12 @@ def encode_without_options(value) # :nodoc:
126171

127172
self.use_standard_json_time_format = true
128173
self.escape_html_entities_in_json = true
129-
self.json_encoder = JSONGemEncoder
174+
self.json_encoder =
175+
if defined?(::JSON::Coder)
176+
JSONGemCoderEncoder
177+
else
178+
JSONGemEncoder
179+
end
130180
self.time_precision = 3
131181
end
132182
end

activesupport/test/json/encoding_test.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,33 @@ def test_to_json_works_on_io_objects
484484
assert_equal STDOUT.to_s.to_json, STDOUT.to_json
485485
end
486486

487+
class AsJSONLoop
488+
def initialize(count)
489+
@count = count
490+
end
491+
492+
def as_json
493+
if @count > 0
494+
@count -= 1
495+
dup
496+
else
497+
self
498+
end
499+
end
500+
end
501+
502+
def test_as_json_infinite_loop
503+
assert_raise SystemStackError do
504+
AsJSONLoop.new(Float::INFINITY).to_json
505+
end
506+
end
507+
508+
def test_as_json_too_recursive
509+
assert_raise SystemStackError do
510+
AsJSONLoop.new(20).to_json
511+
end
512+
end
513+
487514
private
488515
def object_keys(json_object)
489516
json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
@@ -504,3 +531,16 @@ def with_time_precision(value)
504531
ActiveSupport::JSON::Encoding.time_precision = old_value
505532
end
506533
end
534+
535+
if defined?(::JSON::Coder)
536+
class OldJSONEncodingTest < TestJSONEncoding
537+
setup do
538+
@json_encoder = ActiveSupport::JSON::Encoding.json_encoder
539+
ActiveSupport::JSON::Encoding.json_encoder = ActiveSupport::JSON::Encoding::JSONGemEncoder
540+
end
541+
542+
teardown do
543+
ActiveSupport::JSON::Encoding.json_encoder = @json_encoder
544+
end
545+
end
546+
end

0 commit comments

Comments
 (0)