Skip to content

Commit

Permalink
Optimize JSON escaping
Browse files Browse the repository at this point in the history
Instead of using multiple gsub! calls, this uses binary encoding to do
the regexp gsub with map, then forces encoding back to UTF-8.

This uses the same approach as before
ebe0c40, with binary encoding, which
performs better.

Co-authored-by: Jean Boussier <[email protected]>
  • Loading branch information
etiennebarrie and byroot committed Feb 12, 2025
1 parent 8d4cbef commit 75b4380
Showing 1 changed file with 24 additions and 12 deletions.
36 changes: 24 additions & 12 deletions activesupport/lib/active_support/json/encoding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ def encode(value, options = nil)
end

module Encoding # :nodoc:
U2028 = -"\u2028".b
U2029 = -"\u2029".b

ESCAPED_CHARS = {
U2028 => '\u2028'.b,
U2029 => '\u2029'.b,
">".b => '\u003e'.b,
"<".b => '\u003c'.b,
"&".b => '\u0026'.b,
}

ESCAPE_REGEX_WITH_HTML_ENTITIES = Regexp.union(*ESCAPED_CHARS.keys)
ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = Regexp.union(U2028, U2029)

class JSONGemEncoder # :nodoc:
attr_reader :options

Expand All @@ -47,14 +61,13 @@ def encode(value)
# Rails does more escaping than the JSON gem natively does (we
# escape \u2028 and \u2029 and optionally >, <, & to work around
# certain browser problems).
json.force_encoding(::Encoding::BINARY)
if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
json.gsub!(">", '\u003e')
json.gsub!("<", '\u003c')
json.gsub!("&", '\u0026')
json.gsub!(ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS)
else
json.gsub!(ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS)
end
json.gsub!("\u2028", '\u2028')
json.gsub!("\u2029", '\u2029')
json
json.force_encoding(::Encoding::UTF_8)
end

private
Expand Down Expand Up @@ -130,14 +143,13 @@ def encode(value)
# Rails does more escaping than the JSON gem natively does (we
# escape \u2028 and \u2029 and optionally >, <, & to work around
# certain browser problems).
json.force_encoding(::Encoding::BINARY)
if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
json.gsub!(">", '\u003e')
json.gsub!("<", '\u003c')
json.gsub!("&", '\u0026')
json.gsub!(ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS)
else
json.gsub!(ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS)
end
json.gsub!("\u2028", '\u2028')
json.gsub!("\u2029", '\u2029')
json
json.force_encoding(::Encoding::UTF_8)
end
end
end
Expand Down

0 comments on commit 75b4380

Please sign in to comment.