aboutsummaryrefslogblamecommitdiffstats
path: root/activesupport/lib/active_support/core_ext/object/json.rb
blob: 416059d17bc3a0970a0a664be52c4f42de1bc5fd (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
                             
 
                                                              



                     



                                                                     
              


                                                       
 
   




                                                                                                  
 



                                                                                                   
 



                                                                                                 

                    







                                                                            
         



       
                                                                                                                  
                                                              

   


                                     
                              
        
                                      





                            
                                              






                                     





                                     





                                     





                                     











                                     



                                                                              
                                                 














                                                                                 
                                     
                        














                                     





                                     







                                     
                                                            
     
















                                                              
                                                                                      
     



                                     
                                                                  
                                                             







                                                                    
                                                                  








                                     
                                                                  
                                                             
        
                                      



       





                            





                            
                              
                            
                                        

     





                            
# frozen_string_literal: true

# Hack to load json gem first so we can overwrite its to_json.
require "json"
require "bigdecimal"
require "uri/generic"
require "pathname"
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"

#--
# 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.

module ActiveSupport
  module ToJsonWithActiveSupportEncoder # :nodoc:
    def to_json(options = nil)
      if options.is_a?(::JSON::State)
        # Called from JSON.{generate,dump}, forward it to JSON gem's to_json
        super(options)
      else
        # to_json is being invoked directly, use ActiveSupport's encoder
        ActiveSupport::JSON.encode(self, options)
      end
    end
  end
end

[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].reverse_each do |klass|
  klass.prepend(ActiveSupport::ToJsonWithActiveSupportEncoder)
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 IO
  def as_json(options = nil) #:nodoc:
    to_s
  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 URI::Generic #:nodoc:
  def as_json(options = nil)
    to_s
  end
end

class Pathname #:nodoc:
  def as_json(options = nil)
    to_s
  end
end

class Process::Status #:nodoc:
  def as_json(options = nil)
    { exitstatus: exitstatus, pid: pid }
  end
end

class Exception
  def as_json(options = nil)
    to_s
  end
end