Skip to content

Commit dae66a0

Browse files
committed
Merge pull request rails#12203 from chancancode/eager_load_json
Eagerload active_support/json/encoding in active_support/core_ext/object/to_json
2 parents 45318e4 + 64c88fb commit dae66a0

File tree

8 files changed

+242
-227
lines changed

8 files changed

+242
-227
lines changed

activesupport/lib/active_support/core_ext/object.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
require 'active_support/core_ext/object/conversions'
99
require 'active_support/core_ext/object/instance_variables'
1010

11-
require 'active_support/core_ext/object/to_json'
11+
require 'active_support/core_ext/object/json'
1212
require 'active_support/core_ext/object/to_param'
1313
require 'active_support/core_ext/object/to_query'
1414
require 'active_support/core_ext/object/with_options'
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Hack to load json gem first so we can overwrite its to_json.
2+
require 'json'
3+
4+
# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
5+
# their default behavior. That said, we need to define the basic to_json method in all of them,
6+
# otherwise they will always use to_json gem implementation, which is backwards incompatible in
7+
# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
8+
# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
9+
[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
10+
klass.class_eval do
11+
# Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
12+
def to_json(options = nil)
13+
ActiveSupport::JSON.encode(self, options)
14+
end
15+
end
16+
end
17+
18+
class Object
19+
def as_json(options = nil) #:nodoc:
20+
if respond_to?(:to_hash)
21+
to_hash
22+
else
23+
instance_values
24+
end
25+
end
26+
end
27+
28+
class Struct #:nodoc:
29+
def as_json(options = nil)
30+
Hash[members.zip(values)]
31+
end
32+
end
33+
34+
class TrueClass
35+
def as_json(options = nil) #:nodoc:
36+
self
37+
end
38+
39+
def encode_json(encoder) #:nodoc:
40+
to_s
41+
end
42+
end
43+
44+
class FalseClass
45+
def as_json(options = nil) #:nodoc:
46+
self
47+
end
48+
49+
def encode_json(encoder) #:nodoc:
50+
to_s
51+
end
52+
end
53+
54+
class NilClass
55+
def as_json(options = nil) #:nodoc:
56+
self
57+
end
58+
59+
def encode_json(encoder) #:nodoc:
60+
'null'
61+
end
62+
end
63+
64+
class String
65+
def as_json(options = nil) #:nodoc:
66+
self
67+
end
68+
69+
def encode_json(encoder) #:nodoc:
70+
encoder.escape(self)
71+
end
72+
end
73+
74+
class Symbol
75+
def as_json(options = nil) #:nodoc:
76+
to_s
77+
end
78+
end
79+
80+
class Numeric
81+
def as_json(options = nil) #:nodoc:
82+
self
83+
end
84+
85+
def encode_json(encoder) #:nodoc:
86+
to_s
87+
end
88+
end
89+
90+
class Float
91+
# Encoding Infinity or NaN to JSON should return "null". The default returns
92+
# "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]').
93+
def as_json(options = nil) #:nodoc:
94+
finite? ? self : nil
95+
end
96+
end
97+
98+
class BigDecimal
99+
# A BigDecimal would be naturally represented as a JSON number. Most libraries,
100+
# however, parse non-integer JSON numbers directly as floats. Clients using
101+
# those libraries would get in general a wrong number and no way to recover
102+
# other than manually inspecting the string with the JSON code itself.
103+
#
104+
# That's why a JSON string is returned. The JSON literal is not numeric, but
105+
# if the other end knows by contract that the data is supposed to be a
106+
# BigDecimal, it still has the chance to post-process the string and get the
107+
# real value.
108+
#
109+
# Use <tt>ActiveSupport.use_standard_json_big_decimal_format = true</tt> to
110+
# override this behavior.
111+
def as_json(options = nil) #:nodoc:
112+
if finite?
113+
ActiveSupport.encode_big_decimal_as_string ? to_s : self
114+
else
115+
nil
116+
end
117+
end
118+
end
119+
120+
class Regexp
121+
def as_json(options = nil) #:nodoc:
122+
to_s
123+
end
124+
end
125+
126+
module Enumerable
127+
def as_json(options = nil) #:nodoc:
128+
to_a.as_json(options)
129+
end
130+
end
131+
132+
class Range
133+
def as_json(options = nil) #:nodoc:
134+
to_s
135+
end
136+
end
137+
138+
class Array
139+
def as_json(options = nil) #:nodoc:
140+
# use encoder as a proxy to call as_json on all elements, to protect from circular references
141+
encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
142+
map { |v| encoder.as_json(v, options) }
143+
end
144+
145+
def encode_json(encoder) #:nodoc:
146+
# we assume here that the encoder has already run as_json on self and the elements, so we run encode_json directly
147+
"[#{map { |v| v.encode_json(encoder) } * ','}]"
148+
end
149+
end
150+
151+
class Hash
152+
def as_json(options = nil) #:nodoc:
153+
# create a subset of the hash by applying :only or :except
154+
subset = if options
155+
if attrs = options[:only]
156+
slice(*Array(attrs))
157+
elsif attrs = options[:except]
158+
except(*Array(attrs))
159+
else
160+
self
161+
end
162+
else
163+
self
164+
end
165+
166+
# use encoder as a proxy to call as_json on all values in the subset, to protect from circular references
167+
encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
168+
Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
169+
end
170+
171+
def encode_json(encoder) #:nodoc:
172+
# values are encoded with use_options = false, because we don't want hash representations from ActiveModel to be
173+
# processed once again with as_json with options, as this could cause unexpected results (i.e. missing fields);
174+
175+
# on the other hand, we need to run as_json on the elements, because the model representation may contain fields
176+
# like Time/Date in their original (not jsonified) form, etc.
177+
178+
"{#{map { |k,v| "#{encoder.encode(k.to_s)}:#{encoder.encode(v, false)}" } * ','}}"
179+
end
180+
end
181+
182+
class Time
183+
def as_json(options = nil) #:nodoc:
184+
if ActiveSupport.use_standard_json_time_format
185+
xmlschema
186+
else
187+
%(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
188+
end
189+
end
190+
end
191+
192+
class Date
193+
def as_json(options = nil) #:nodoc:
194+
if ActiveSupport.use_standard_json_time_format
195+
strftime("%Y-%m-%d")
196+
else
197+
strftime("%Y/%m/%d")
198+
end
199+
end
200+
end
201+
202+
class DateTime
203+
def as_json(options = nil) #:nodoc:
204+
if ActiveSupport.use_standard_json_time_format
205+
xmlschema
206+
else
207+
strftime('%Y/%m/%d %H:%M:%S %z')
208+
end
209+
end
210+
end
211+
212+
class Process::Status
213+
def as_json(options = nil)
214+
{ :exitstatus => exitstatus, :pid => pid }
215+
end
216+
end
Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,5 @@
1-
# Hack to load json gem first so we can overwrite its to_json.
2-
begin
3-
require 'json'
4-
rescue LoadError
5-
end
1+
ActiveSupport::Deprecation.warn 'You have required `active_support/core_ext/object/to_json`. ' \
2+
'This file will be removed in Rails 4.2. You should require `active_support/core_ext/object/json` ' \
3+
'instead.'
64

7-
# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
8-
# their default behavior. That said, we need to define the basic to_json method in all of them,
9-
# otherwise they will always use to_json gem implementation, which is backwards incompatible in
10-
# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
11-
# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
12-
[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
13-
klass.class_eval do
14-
# Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
15-
def to_json(options = nil)
16-
ActiveSupport::JSON.encode(self, options)
17-
end
18-
end
19-
end
20-
21-
module Process
22-
class Status
23-
def as_json(options = nil)
24-
{ :exitstatus => exitstatus, :pid => pid }
25-
end
26-
end
27-
end
5+
require 'active_support/core_ext/object/json'

0 commit comments

Comments
 (0)