Skip to content

Commit 3626b8f

Browse files
authored
Merge pull request rails#54748 from byroot/json-escape-option
Skip JSON escaping when writing into JSON columns
2 parents 6204a55 + 9061627 commit 3626b8f

File tree

4 files changed

+31
-7
lines changed

4 files changed

+31
-7
lines changed

activerecord/lib/active_record/coders/json.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
# frozen_string_literal: true
22

3+
require "active_support/json"
4+
35
module ActiveRecord
46
module Coders # :nodoc:
57
class JSON # :nodoc:
6-
def initialize(options = {})
7-
@options = options
8+
DEFAULT_OPTIONS = { escape: false }.freeze
9+
10+
def initialize(options = nil)
11+
@options = options ? DEFAULT_OPTIONS.merge(options) : DEFAULT_OPTIONS
12+
@encoder = ActiveSupport::JSON::Encoding.json_encoder.new(options)
813
end
914

1015
def dump(obj)
11-
ActiveSupport::JSON.encode(obj)
16+
@encoder.encode(obj)
1217
end
1318

1419
def load(json)

activerecord/lib/active_record/type/json.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "active_support/json"
4+
35
module ActiveRecord
46
module Type
57
class Json < ActiveModel::Type::Value
@@ -14,8 +16,10 @@ def deserialize(value)
1416
ActiveSupport::JSON.decode(value) rescue nil
1517
end
1618

19+
JSON_ENCODER = ActiveSupport::JSON::Encoding.json_encoder.new(escape: false)
20+
1721
def serialize(value)
18-
ActiveSupport::JSON.encode(value) unless value.nil?
22+
JSON_ENCODER.encode(value) unless value.nil?
1923
end
2024

2125
def changed_in_place?(raw_old_value, new_value)

activesupport/lib/active_support/json/encoding.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ class << self
2020
# ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
2121
# # => "{\"team\":\"rails\",\"players\":\"36\"}"
2222
#
23-
# Generates JSON that is safe to include in JavaScript as it escapes
24-
# U+2028 (Line Separator) and U+2029 (Paragraph Separator):
23+
# By default, it generates JSON that is safe to include in JavaScript, as
24+
# it escapes U+2028 (Line Separator) and U+2029 (Paragraph Separator):
2525
#
2626
# ActiveSupport::JSON.encode({ key: "\u2028" })
2727
# # => "{\"key\":\"\\u2028\"}"
@@ -32,11 +32,17 @@ class << self
3232
# ActiveSupport::JSON.encode({ key: "<>&" })
3333
# # => "{\"key\":\"\\u003c\\u003e\\u0026\"}"
3434
#
35-
# This can be changed with the +escape_html_entities+ option, or the
35+
# This behavior can be changed with the +escape_html_entities+ option, or the
3636
# global escape_html_entities_in_json configuration option.
3737
#
3838
# ActiveSupport::JSON.encode({ key: "<>&" }, escape_html_entities: false)
3939
# # => "{\"key\":\"<>&\"}"
40+
#
41+
# For performance reasons, you can set the +escape+ option to false,
42+
# which will skip all escaping:
43+
#
44+
# ActiveSupport::JSON.encode({ key: "\u2028<>&" }, escape: false)
45+
# # => "{\"key\":\"\u2028<>&\"}"
4046
def encode(value, options = nil)
4147
if options.nil? || options.empty?
4248
Encoding.encode_without_options(value)
@@ -76,6 +82,8 @@ def encode(value)
7682
end
7783
json = stringify(jsonify(value))
7884

85+
return json unless @options.fetch(:escape, true)
86+
7987
# Rails does more escaping than the JSON gem natively does (we
8088
# escape \u2028 and \u2029 and optionally >, <, & to work around
8189
# certain browser problems).
@@ -162,6 +170,8 @@ def encode(value)
162170

163171
json = CODER.dump(value)
164172

173+
return json unless @options.fetch(:escape, true)
174+
165175
# Rails does more escaping than the JSON gem natively does (we
166176
# escape \u2028 and \u2029 and optionally >, <, & to work around
167177
# certain browser problems).

activesupport/test/json/encoding_test.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ def test_hash_encoding
5252
assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(a: :b, c: :d))
5353
end
5454

55+
def test_unicode_escape
56+
assert_equal %{{"\\u2028":"\\u2029"}}, ActiveSupport::JSON.encode("\u2028" => "\u2029")
57+
assert_equal %{{"\u2028":"\u2029"}}, ActiveSupport::JSON.encode({ "\u2028" => "\u2029" }, escape: false)
58+
end
59+
5560
def test_hash_keys_encoding
5661
ActiveSupport.escape_html_entities_in_json = true
5762
assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>")

0 commit comments

Comments
 (0)