# Hack to load json gem first so we can overwrite its to_json. require 'json' require 'bigdecimal' require 'active_support/core_ext/big_decimal/conversions' # for #to_s require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/instance_variables' 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, Enumerable].each do |klass| klass.class_eval do def to_json_with_active_support_encoder(options = nil) # :nodoc: 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 class Object def as_json(options = nil) #:nodoc: if respond_to?(:to_hash) to_hash.as_json(options) else instance_values.as_json(options) end end end class Struct #:nodoc: def as_json(options = nil) Hash[members.zip(values)].as_json(options) end end class TrueClass def as_json(options = nil) #:nodoc: self end end class FalseClass def as_json(options = nil) #:nodoc: self end end class NilClass def as_json(options = nil) #:nodoc: self end end class String def as_json(options = nil) #:nodoc: self end end class Symbol def as_json(options = nil) #:nodoc: to_s end end class Numeric def as_json(options = nil) #:nodoc: self end end class Float # Encoding Infinity or NaN to JSON should return "null". The default returns # "Infinity" or "NaN" which are not valid JSON. def as_json(options = nil) #:nodoc: finite? ? self : nil end end class BigDecimal # A BigDecimal would be naturally represented as a JSON number. Most libraries, # however, parse non-integer JSON numbers directly as floats. Clients using # those libraries would get in general a wrong number and no way to recover # other than manually inspecting the string with the JSON code itself. # # That's why a JSON string is returned. The JSON literal is not numeric, but # if the other end knows by contract that the data is supposed to be a # BigDecimal, it still has the chance to post-process the string and get the # real value. def as_json(options = nil) #:nodoc: finite? ? to_s : nil end end class Regexp def as_json(options = nil) #:nodoc: to_s end end module Enumerable def as_json(options = nil) #:nodoc: to_a.as_json(options) end end class Range def as_json(options = nil) #:nodoc: to_s end end class Array def as_json(options = nil) #:nodoc: map { |v| options ? v.as_json(options.dup) : v.as_json } end end class Hash def as_json(options = nil) #:nodoc: # create a subset of the hash by applying :only or :except subset = if options if attrs = options[:only] slice(*Array(attrs)) elsif attrs = options[:except] except(*Array(attrs)) else self end else self end Hash[subset.map { |k, v| [k.to_s, options ? v.as_json(options.dup) : v.as_json] }] end end class Time def as_json(options = nil) #:nodoc: if ActiveSupport::JSON::Encoding.use_standard_json_time_format xmlschema(ActiveSupport::JSON::Encoding.time_precision) else %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) end end end class Date def as_json(options = nil) #:nodoc: if ActiveSupport::JSON::Encoding.use_standard_json_time_format strftime("%Y-%m-%d") else strftime("%Y/%m/%d") end end end class DateTime def as_json(options = nil) #:nodoc: if ActiveSupport::JSON::Encoding.use_standard_json_time_format xmlschema(ActiveSupport::JSON::Encoding.time_precision) else strftime('%Y/%m/%d %H:%M:%S %z') end end end class Process::Status #:nodoc: def as_json(options = nil) { :exitstatus => exitstatus, :pid => pid } end end