aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/json/encoding.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/json/encoding.rb')
-rw-r--r--activesupport/lib/active_support/json/encoding.rb134
1 files changed, 134 insertions, 0 deletions
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
new file mode 100644
index 0000000000..de1b8ac8cf
--- /dev/null
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+require "active_support/core_ext/object/json"
+require "active_support/core_ext/module/delegation"
+
+module ActiveSupport
+ class << self
+ delegate :use_standard_json_time_format, :use_standard_json_time_format=,
+ :time_precision, :time_precision=,
+ :escape_html_entities_in_json, :escape_html_entities_in_json=,
+ :json_encoder, :json_encoder=,
+ to: :'ActiveSupport::JSON::Encoding'
+ end
+
+ module JSON
+ # Dumps objects in JSON (JavaScript Object Notation).
+ # See http://www.json.org for more info.
+ #
+ # ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
+ # # => "{\"team\":\"rails\",\"players\":\"36\"}"
+ def self.encode(value, options = nil)
+ Encoding.json_encoder.new(options).encode(value)
+ end
+
+ module Encoding #:nodoc:
+ class JSONGemEncoder #:nodoc:
+ attr_reader :options
+
+ def initialize(options = nil)
+ @options = options || {}
+ end
+
+ # Encode the given object into a JSON string
+ def encode(value)
+ stringify jsonify value.as_json(options.dup)
+ end
+
+ private
+ # Rails does more escaping than the JSON gem natively does (we
+ # escape \u2028 and \u2029 and optionally >, <, & to work around
+ # certain browser problems).
+ ESCAPED_CHARS = {
+ "\u2028" => '\u2028',
+ "\u2029" => '\u2029',
+ ">" => '\u003e',
+ "<" => '\u003c',
+ "&" => '\u0026',
+ }
+
+ ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u
+ ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u
+
+ # This class wraps all the strings we see and does the extra escaping
+ class EscapedString < String #:nodoc:
+ def to_json(*)
+ if Encoding.escape_html_entities_in_json
+ s = super
+ s.gsub! ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS
+ s
+ else
+ s = super
+ s.gsub! ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
+ s
+ end
+ end
+
+ def to_s
+ self
+ end
+ end
+
+ # Mark these as private so we don't leak encoding-specific constructs
+ private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES,
+ :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString
+
+ # Convert an object into a "JSON-ready" representation composed of
+ # primitives like Hash, Array, String, Numeric,
+ # and +true+/+false+/+nil+.
+ # Recursively calls #as_json to the object to recursively build a
+ # fully JSON-ready object.
+ #
+ # This allows developers to implement #as_json without having to
+ # worry about what base types of objects they are allowed to return
+ # or having to remember to call #as_json recursively.
+ #
+ # Note: the +options+ hash passed to +object.to_json+ is only passed
+ # to +object.as_json+, not any of this method's recursive +#as_json+
+ # calls.
+ def jsonify(value)
+ case value
+ when String
+ EscapedString.new(value)
+ when Numeric, NilClass, TrueClass, FalseClass
+ value.as_json
+ when Hash
+ Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
+ when Array
+ value.map { |v| jsonify(v) }
+ else
+ jsonify value.as_json
+ end
+ end
+
+ # Encode a "jsonified" Ruby data structure using the JSON gem
+ def stringify(jsonified)
+ ::JSON.generate(jsonified, quirks_mode: true, max_nesting: false)
+ end
+ end
+
+ class << self
+ # If true, use ISO 8601 format for dates and times. Otherwise, fall back
+ # to the Active Support legacy format.
+ attr_accessor :use_standard_json_time_format
+
+ # If true, encode >, <, & as escaped unicode sequences (e.g. > as \u003e)
+ # as a safety measure.
+ attr_accessor :escape_html_entities_in_json
+
+ # Sets the precision of encoded time values.
+ # Defaults to 3 (equivalent to millisecond precision)
+ attr_accessor :time_precision
+
+ # Sets the encoder used by Rails to encode Ruby objects into JSON strings
+ # in +Object#to_json+ and +ActiveSupport::JSON.encode+.
+ attr_accessor :json_encoder
+ end
+
+ self.use_standard_json_time_format = true
+ self.escape_html_entities_in_json = true
+ self.json_encoder = JSONGemEncoder
+ self.time_precision = 3
+ end
+ end
+end