1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Improved compatibility with the stdlib JSON gem.

Previously, calling `::JSON.{generate,dump}` sometimes causes
unexpected failures such as intridea/multi_json#86.

`::JSON.{generate,dump}` now bypasses the ActiveSupport JSON encoder
completely and yields the same result with or without ActiveSupport.
This means that it will **not** call `as_json` and will ignore any
options that the JSON gem does not natively understand. To invoke
ActiveSupport's JSON encoder instead, use `obj.to_json(options)` or
`ActiveSupport::JSON.encode(obj, options)`.
This commit is contained in:
Godfrey Chan 2013-11-12 01:57:21 -08:00
parent 07996ebc50
commit 0f33d70e89
3 changed files with 80 additions and 3 deletions

View file

@ -1,3 +1,17 @@
* Improved compatibility with the stdlib JSON gem.
Previously, calling `::JSON.{generate,dump}` sometimes causes unexpected
failures such as intridea/multi_json#86.
`::JSON.{generate,dump}` now bypasses the ActiveSupport JSON encoder
completely and yields the same result with or without ActiveSupport. This
means that it will **not** call `as_json` and will ignore any options that
the JSON gem does not natively understand. To invoke ActiveSupport's JSON
encoder instead, use `obj.to_json(options)` or
`ActiveSupport::JSON.encode(obj, options)`.
*Godfrey Chan*
* Fix Active Support `Time#to_json` and `DateTime#to_json` to return 3 decimal
places worth of fractional seconds, similar to `TimeWithZone`.

View file

@ -9,18 +9,36 @@ require 'time'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/module/aliasing'
# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
# their default behavior. That said, we need to define the basic to_json method in all of them,
# otherwise they will always use to_json gem implementation, which is backwards incompatible in
# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
#
# On the other hand, we should avoid conflict with ::JSON.{generate,dump}(obj). Unfortunately, the
# JSON gem's encoder relies on its own to_json implementation to encode objects. Since it always
# passes a ::JSON::State object as the only argument to to_json, we can detect that and forward the
# calls to the original to_json method.
#
# It should be noted that when using ::JSON.{generate,dump} directly, ActiveSupport's encoder is
# bypassed completely. This means that as_json won't be invoked and the JSON gem will simply
# ignore any options it does not natively understand. This also means that ::JSON.{generate,dump}
# should give exactly the same results with or without active support.
[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
klass.class_eval do
# Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
def to_json(options = nil)
ActiveSupport::JSON.encode(self, options)
def to_json_with_active_support_encoder(options = nil)
if options.is_a?(::JSON::State)
# Called from JSON.{generate,dump}, forward it to JSON gem's to_json
self.to_json_without_active_support_encoder(options)
else
# to_json is being invoked directly, use ActiveSupport's encoder
ActiveSupport::JSON.encode(self, options)
end
end
alias_method_chain :to_json, :active_support_encoder
end
end

View file

@ -32,6 +32,19 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
end
class HashWithAsJson < Hash
attr_accessor :as_json_called
def initialize(*)
super
end
def as_json(options={})
@as_json_called = true
super
end
end
TrueTests = [[ true, %(true) ]]
FalseTests = [[ false, %(false) ]]
NilTests = [[ nil, %(null) ]]
@ -367,6 +380,38 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal false, false.as_json
end
def test_json_gem_dump_by_passing_active_support_encoder
h = HashWithAsJson.new
h[:foo] = "hello"
h[:bar] = "world"
assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
assert_nil h.as_json_called
end
def test_json_gem_generate_by_passing_active_support_encoder
h = HashWithAsJson.new
h[:foo] = "hello"
h[:bar] = "world"
assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
assert_nil h.as_json_called
end
def test_json_gem_pretty_generate_by_passing_active_support_encoder
h = HashWithAsJson.new
h[:foo] = "hello"
h[:bar] = "world"
assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
{
"foo": "hello",
"bar": "world"
}
EXPECTED
assert_nil h.as_json_called
end
protected
def object_keys(json_object)