1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
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=,
:escape_html_entities_in_json, :escape_html_entities_in_json=,
:encode_big_decimal_as_string, :encode_big_decimal_as_string=,
:json_encoder, :json_encoder=,
:to => :'ActiveSupport::JSON::Encoding'
end
module JSON
# Dumps objects in JSON (JavaScript Object Notation).
# See 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
super.gsub ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS
else
super.gsub ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
end
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
# Recursively turn the given object into a "jsonified" Ruby data structure
# that the JSON gem understands - i.e. we want only Hash, Array, String,
# Numeric, true, false and nil in the final tree. Calls #as_json on it if
# it's not from one of these base types.
#
# This allows developers to implement #as_json withouth having to worry
# about what base types of objects they are allowed to return and having
# to remember calling #as_json recursively.
#
# By default, the options hash is not passed to the children data structures
# to avoid undesiarable result. Develoers must opt-in by implementing
# custom #as_json methods (e.g. Hash#as_json and Array#as_json).
def jsonify(value)
if value.is_a?(Hash)
Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
elsif value.is_a?(Array)
value.map { |v| jsonify(v) }
elsif value.is_a?(String)
EscapedString.new(value)
elsif value.is_a?(Numeric)
value
elsif value == true
true
elsif value == false
false
elsif value == nil
nil
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 encoder used by Rails to encode Ruby objects into JSON strings
# in +Object#to_json+ and +ActiveSupport::JSON.encode+.
attr_accessor :json_encoder
def encode_big_decimal_as_string=(as_string)
message = \
"The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
"the new encoder will always encode them as strings.\n\n" \
"You are seeing this error because you have 'active_support.encode_big_decimal_as_string' in " \
"your configuration file. If you have been setting this to true, you can safely remove it from " \
"your configuration. Otherwise, you should add the 'activesupport-json_encoder' gem to your " \
"Gemfile in order to restore this functionality."
raise NotImplementedError, message
end
def encode_big_decimal_as_string
message = \
"The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
"the new encoder will always encode them as strings.\n\n" \
"You are seeing this error because you are trying to check the value of the related configuration, " \
"'active_support.encode_big_decimal_as_string'. If your application depends on this option, you should " \
"add the 'activesupport-json_encoder' gem to your Gemfile. For now, this option will always be true. " \
"In the future, it will be removed from Rails, so you should stop checking its value."
ActiveSupport::Deprecation.warn message
true
end
# Deprecate CircularReferenceError
def const_missing(name)
if name == :CircularReferenceError
message = "The JSON encoder in Rails 4.1 no longer offers protection from circular references. " \
"You are seeing this warning because you are rescuing from (or otherwise referencing) " \
"ActiveSupport::Encoding::CircularReferenceError. In the future, this error will be " \
"removed from Rails. You should remove these rescue blocks from your code and ensure " \
"that your data structures are free of circular references so they can be properly " \
"serialized into JSON.\n\n" \
"For example, the following Hash contains a circular reference to itself:\n" \
" h = {}\n" \
" h['circular'] = h\n" \
"In this case, calling h.to_json would not work properly."
ActiveSupport::Deprecation.warn message
SystemStackError
else
super
end
end
end
self.use_standard_json_time_format = true
self.escape_html_entities_in_json = true
self.json_encoder = JSONGemEncoder
end
end
end
|