diff options
Diffstat (limited to 'activesupport/lib')
17 files changed, 262 insertions, 317 deletions
diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb index 6d845182fb..3e1d9b1d33 100644 --- a/activesupport/lib/active_support/json.rb +++ b/activesupport/lib/active_support/json.rb @@ -1,87 +1,2 @@ -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/module/attribute_accessors' - -module ActiveSupport - # If true, use ISO 8601 format for dates and times. Otherwise, fall back to the Active Support legacy format. - mattr_accessor :use_standard_json_time_format - # Look for and parse json strings that look like ISO 8601 times. - mattr_accessor :parse_json_times - - module JSON - # matches YAML-formatted dates - DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[ \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/ - - module Encoding #:nodoc: - mattr_accessor :escape_regex - - ESCAPED_CHARS = { - "\010" => '\b', - "\f" => '\f', - "\n" => '\n', - "\r" => '\r', - "\t" => '\t', - '"' => '\"', - '\\' => '\\\\', - '>' => '\u003E', - '<' => '\u003C', - '&' => '\u0026' - } - - def self.escape(string) - string = string.dup.force_encoding(::Encoding::BINARY) if string.respond_to?(:force_encoding) - json = string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] }. - gsub(/([\xC0-\xDF][\x80-\xBF]| - [\xE0-\xEF][\x80-\xBF]{2}| - [\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s| - s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/, '\\\\u\&') - } - %("#{json}") - end - end - - class << self - delegate :decode, :to => :backend - - def backend - unless defined? @backend - self.backend = defined?(::JSON) ? "JSONGem" : "Yaml" - end - @backend - end - - def backend=(name) - if name.is_a?(Module) - @backend = name - else - require "active_support/json/backends/#{name.to_s.downcase}.rb" - @backend = ActiveSupport::JSON::Backends::const_get(name) - end - end - - def with_backend(name) - old_backend, self.backend = backend, name - yield - ensure - self.backend = old_backend - end - end - end - - class << self - attr_reader :escape_html_entities_in_json - - def escape_html_entities_in_json=(value) - ActiveSupport::JSON::Encoding.escape_regex = \ - if value - /[\010\f\n\r\t"\\><&]/ - else - /[\010\f\n\r\t"\\]/ - end - @escape_html_entities_in_json = value - end - end -end - -ActiveSupport.escape_html_entities_in_json = true - +require 'active_support/json/decoding' require 'active_support/json/encoding' diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb new file mode 100644 index 0000000000..5d4caa362f --- /dev/null +++ b/activesupport/lib/active_support/json/decoding.rb @@ -0,0 +1,35 @@ +require 'active_support/core_ext/module/attribute_accessors' + +module ActiveSupport + # Look for and parse json strings that look like ISO 8601 times. + mattr_accessor :parse_json_times + + module JSON + class << self + delegate :decode, :to => :backend + + def backend + @backend || begin + self.backend = "Yaml" + @backend + end + end + + def backend=(name) + if name.is_a?(Module) + @backend = name + else + require "active_support/json/backends/#{name.to_s.downcase}.rb" + @backend = ActiveSupport::JSON::Backends::const_get(name) + end + end + + def with_backend(name) + old_backend, self.backend = backend, name + yield + ensure + self.backend = old_backend + end + end + end +end diff --git a/activesupport/lib/active_support/json/encoders/date.rb b/activesupport/lib/active_support/json/encoders/date.rb deleted file mode 100644 index 9adb3c20e2..0000000000 --- a/activesupport/lib/active_support/json/encoders/date.rb +++ /dev/null @@ -1,22 +0,0 @@ -class Date - private - # Returns a JSON string representing the date. If ActiveSupport.use_standard_json_time_format is set to true, the - # ISO 8601 format is used. - # - # ==== Examples - # - # # With ActiveSupport.use_standard_json_time_format = true - # Date.new(2005,2,1).to_json - # # => "2005-02-01" - # - # # With ActiveSupport.use_standard_json_time_format = false - # Date.new(2005,2,1).to_json - # # => "2005/02/01" - def rails_to_json(*) - if ActiveSupport.use_standard_json_time_format - %("#{strftime("%Y-%m-%d")}") - else - %("#{strftime("%Y/%m/%d")}") - end - end -end diff --git a/activesupport/lib/active_support/json/encoders/date_time.rb b/activesupport/lib/active_support/json/encoders/date_time.rb deleted file mode 100644 index 3a29292b24..0000000000 --- a/activesupport/lib/active_support/json/encoders/date_time.rb +++ /dev/null @@ -1,22 +0,0 @@ -class DateTime - private - # Returns a JSON string representing the datetime. If ActiveSupport.use_standard_json_time_format is set to true, the - # ISO 8601 format is used. - # - # ==== Examples - # - # # With ActiveSupport.use_standard_json_time_format = true - # DateTime.civil(2005,2,1,15,15,10).to_json - # # => "2005-02-01T15:15:10+00:00" - # - # # With ActiveSupport.use_standard_json_time_format = false - # DateTime.civil(2005,2,1,15,15,10).to_json - # # => "2005/02/01 15:15:10 +0000" - def rails_to_json(*) - if ActiveSupport.use_standard_json_time_format - xmlschema.inspect - else - strftime('"%Y/%m/%d %H:%M:%S %z"') - end - end -end diff --git a/activesupport/lib/active_support/json/encoders/enumerable.rb b/activesupport/lib/active_support/json/encoders/enumerable.rb deleted file mode 100644 index 898990a59c..0000000000 --- a/activesupport/lib/active_support/json/encoders/enumerable.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Enumerable - private - # Returns a JSON string representing the enumerable. Any +options+ - # given will be passed on to its elements. For example: - # - # users = User.find(:all) - # # => users.to_json(:only => :name) - # - # will pass the <tt>:only => :name</tt> option to each user. - def rails_to_json(options = nil, *args) #:nodoc: - "[#{map { |value| ActiveSupport::JSON.encode(value, options, *args) } * ','}]" - end -end diff --git a/activesupport/lib/active_support/json/encoders/false_class.rb b/activesupport/lib/active_support/json/encoders/false_class.rb deleted file mode 100644 index eb975fe542..0000000000 --- a/activesupport/lib/active_support/json/encoders/false_class.rb +++ /dev/null @@ -1,6 +0,0 @@ -class FalseClass - private - def rails_to_json(*) - 'false' - end -end diff --git a/activesupport/lib/active_support/json/encoders/hash.rb b/activesupport/lib/active_support/json/encoders/hash.rb deleted file mode 100644 index 4771484843..0000000000 --- a/activesupport/lib/active_support/json/encoders/hash.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'active_support/core_ext/array/wrap' - -class Hash - private - # Returns a JSON string representing the hash. - # - # Without any +options+, the returned JSON string will include all - # the hash keys. For example: - # - # { :name => "Konata Izumi", 'age' => 16, 1 => 2 }.to_json - # # => {"name": "Konata Izumi", "1": 2, "age": 16} - # - # The keys in the JSON string are unordered due to the nature of hashes. - # - # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the - # attributes included, and will accept 1 or more hash keys to include/exclude. - # - # { :name => "Konata Izumi", 'age' => 16, 1 => 2 }.to_json(:only => [:name, 'age']) - # # => {"name": "Konata Izumi", "age": 16} - # - # { :name => "Konata Izumi", 'age' => 16, 1 => 2 }.to_json(:except => 1) - # # => {"name": "Konata Izumi", "age": 16} - # - # The +options+ also filter down to any hash values. This is particularly - # useful for converting hashes containing ActiveRecord objects or any object - # that responds to options in their <tt>to_json</tt> method. For example: - # - # users = User.find(:all) - # { :users => users, :count => users.size }.to_json(:include => :posts) - # - # would pass the <tt>:include => :posts</tt> option to <tt>users</tt>, - # allowing the posts association in the User model to be converted to JSON - # as well. - def rails_to_json(options = nil, *args) #:nodoc: - hash_keys = self.keys - - if options - if except = options[:except] - hash_keys = hash_keys - Array.wrap(except) - elsif only = options[:only] - hash_keys = hash_keys & Array.wrap(only) - end - end - - result = '{' - result << hash_keys.map do |key| - "#{ActiveSupport::JSON.encode(key.to_s)}:#{ActiveSupport::JSON.encode(self[key], options, *args)}" - end * ',' - result << '}' - end -end diff --git a/activesupport/lib/active_support/json/encoders/nil_class.rb b/activesupport/lib/active_support/json/encoders/nil_class.rb deleted file mode 100644 index 8c51dba384..0000000000 --- a/activesupport/lib/active_support/json/encoders/nil_class.rb +++ /dev/null @@ -1,6 +0,0 @@ -class NilClass - private - def rails_to_json(*) - 'null' - end -end diff --git a/activesupport/lib/active_support/json/encoders/numeric.rb b/activesupport/lib/active_support/json/encoders/numeric.rb deleted file mode 100644 index c7cd0df1d7..0000000000 --- a/activesupport/lib/active_support/json/encoders/numeric.rb +++ /dev/null @@ -1,6 +0,0 @@ -class Numeric - private - def rails_to_json(*) - to_s - end -end diff --git a/activesupport/lib/active_support/json/encoders/object.rb b/activesupport/lib/active_support/json/encoders/object.rb deleted file mode 100644 index 9cc12d91ac..0000000000 --- a/activesupport/lib/active_support/json/encoders/object.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'active_support/core_ext/object/instance_variables' - -class Object - # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info. - def to_json(options = nil) - ActiveSupport::JSON.encode(self, options) - end - - private - def rails_to_json(*args) - ActiveSupport::JSON.encode(instance_values, *args) - end -end diff --git a/activesupport/lib/active_support/json/encoders/regexp.rb b/activesupport/lib/active_support/json/encoders/regexp.rb deleted file mode 100644 index ee42db4d02..0000000000 --- a/activesupport/lib/active_support/json/encoders/regexp.rb +++ /dev/null @@ -1,6 +0,0 @@ -class Regexp - private - def rails_to_json(*) - inspect - end -end diff --git a/activesupport/lib/active_support/json/encoders/string.rb b/activesupport/lib/active_support/json/encoders/string.rb deleted file mode 100644 index 4a6b21c1c0..0000000000 --- a/activesupport/lib/active_support/json/encoders/string.rb +++ /dev/null @@ -1,6 +0,0 @@ -class String - private - def rails_to_json(*) - ActiveSupport::JSON::Encoding.escape(self) - end -end diff --git a/activesupport/lib/active_support/json/encoders/symbol.rb b/activesupport/lib/active_support/json/encoders/symbol.rb deleted file mode 100644 index d575350a4e..0000000000 --- a/activesupport/lib/active_support/json/encoders/symbol.rb +++ /dev/null @@ -1,6 +0,0 @@ -class Symbol - private - def rails_to_json(*args) - ActiveSupport::JSON.encode(to_s, *args) - end -end diff --git a/activesupport/lib/active_support/json/encoders/time.rb b/activesupport/lib/active_support/json/encoders/time.rb deleted file mode 100644 index d434b9aace..0000000000 --- a/activesupport/lib/active_support/json/encoders/time.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'active_support/core_ext/time/conversions' - -class Time - private - # Returns a JSON string representing the time. If ActiveSupport.use_standard_json_time_format is set to true, the - # ISO 8601 format is used. - # - # ==== Examples - # - # # With ActiveSupport.use_standard_json_time_format = true - # Time.utc(2005,2,1,15,15,10).to_json - # # => "2005-02-01T15:15:10Z" - # - # # With ActiveSupport.use_standard_json_time_format = false - # Time.utc(2005,2,1,15,15,10).to_json - # # => "2005/02/01 15:15:10 +0000" - def rails_to_json(*) - if ActiveSupport.use_standard_json_time_format - xmlschema.inspect - else - %("#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}") - end - end -end diff --git a/activesupport/lib/active_support/json/encoders/true_class.rb b/activesupport/lib/active_support/json/encoders/true_class.rb deleted file mode 100644 index bc25a6db78..0000000000 --- a/activesupport/lib/active_support/json/encoders/true_class.rb +++ /dev/null @@ -1,6 +0,0 @@ -class TrueClass - private - def rails_to_json(*) - 'true' - end -end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 5fefe5b88b..97e573f7a6 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -1,33 +1,214 @@ +require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/object/instance_variables' +require 'active_support/deprecation' + +# Hack to load json gem first so we can overwrite its to_json. +begin + require 'json' +rescue LoadError +end + 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=, + :to => :'ActiveSupport::JSON::Encoding' + end + module JSON - class CircularReferenceError < StandardError + # matches YAML-formatted dates + DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[ \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/ + + # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info. + def self.encode(value, options = nil) + Encoding::Encoder.new(options).encode(value) end - # Converts a Ruby object into a JSON string. - def self.encode(value, options = nil, seen = nil) - seen ||= [] - if seen.any? { |object| object.equal?(value) } - raise CircularReferenceError, 'object references itself' + module Encoding #:nodoc: + class CircularReferenceError < StandardError; end + + class Encoder + attr_reader :options + + def initialize(options = nil) + @options = options + @seen = [] + end + + def encode(value) + check_for_circular_references(value) do + value.as_json(options).encode_json(self) + end + end + + def escape(string) + Encoding.escape(string) + end + + private + def check_for_circular_references(value) + if @seen.any? { |object| object.equal?(value) } + raise CircularReferenceError, 'object references itself' + end + @seen.unshift value + yield + ensure + @seen.shift + end + end + + + ESCAPED_CHARS = { + "\010" => '\b', + "\f" => '\f', + "\n" => '\n', + "\r" => '\r', + "\t" => '\t', + '"' => '\"', + '\\' => '\\\\', + '>' => '\u003E', + '<' => '\u003C', + '&' => '\u0026' } + + 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 + + attr_accessor :escape_regex + attr_reader :escape_html_entities_in_json + + def escape_html_entities_in_json=(value) + self.escape_regex = \ + if @escape_html_entities_in_json = value + /[\010\f\n\r\t"\\><&]/ + else + /[\010\f\n\r\t"\\]/ + end + end + + def escape(string) + json = '"' + string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] } + json.force_encoding('ascii-8bit') if respond_to?(:force_encoding) + json.gsub(/([\xC0-\xDF][\x80-\xBF]| + [\xE0-\xEF][\x80-\xBF]{2}| + [\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s| + s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/, '\\\\u\&') + } + '"' + end + end + + self.escape_html_entities_in_json = true + end + + CircularReferenceError = Deprecation::DeprecatedConstantProxy.new('ActiveSupport::JSON::CircularReferenceError', Encoding::CircularReferenceError) + end +end + +class Object + # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info. + def to_json(options = nil) + ActiveSupport::JSON.encode(self, options) + end + + def as_json(options = nil) instance_values end #:nodoc: +end + +# A string that returns itself as its JSON-encoded form. +class ActiveSupport::JSON::Variable < String + def as_json(options = nil) self end #:nodoc: + def encode_json(encoder) self end #:nodoc: +end + +class TrueClass + AS_JSON = ActiveSupport::JSON::Variable.new('true').freeze + def as_json(options = nil) AS_JSON end #:nodoc: +end + +class FalseClass + AS_JSON = ActiveSupport::JSON::Variable.new('false').freeze + def as_json(options = nil) AS_JSON end #:nodoc: +end + +class NilClass + AS_JSON = ActiveSupport::JSON::Variable.new('null').freeze + def as_json(options = nil) AS_JSON end #:nodoc: +end + +class String + def as_json(options = nil) self end #:nodoc: + def encode_json(encoder) encoder.escape(self) end #:nodoc: +end + +class Symbol + def as_json(options = nil) to_s end #:nodoc: +end + +class Numeric + def as_json(options = nil) self end #:nodoc: + def encode_json(encoder) to_s end #:nodoc: +end + +class Regexp + def as_json(options = nil) self end #:nodoc: + def encode_json(encoder) inspect end #:nodoc: +end + +module Enumerable + def as_json(options = nil) to_a end #:nodoc: +end + +class Array + def as_json(options = nil) self end #:nodoc: + def encode_json(encoder) "[#{map { |v| encoder.encode(v) } * ','}]" end #:nodoc: +end + +class Hash + def as_json(options = nil) #:nodoc: + if options + if attrs = options[:only] + slice(*Array.wrap(attrs)) + elsif attrs = options[:except] + except(*Array.wrap(attrs)) + else + self end - seen << value - value.__send__(:rails_to_json, options, seen) - ensure - seen.pop + else + self + end + end + + def encode_json(encoder) + "{#{map { |k,v| "#{encoder.encode(k.to_s)}:#{encoder.encode(v)}" } * ','}}" + end +end + +class Time + def as_json(options = nil) #:nodoc: + if ActiveSupport.use_standard_json_time_format + xmlschema + else + %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) end end end -require 'active_support/json/variable' -require 'active_support/json/encoders/date' -require 'active_support/json/encoders/date_time' -require 'active_support/json/encoders/enumerable' -require 'active_support/json/encoders/false_class' -require 'active_support/json/encoders/hash' -require 'active_support/json/encoders/nil_class' -require 'active_support/json/encoders/numeric' -require 'active_support/json/encoders/object' -require 'active_support/json/encoders/regexp' -require 'active_support/json/encoders/string' -require 'active_support/json/encoders/symbol' -require 'active_support/json/encoders/time' -require 'active_support/json/encoders/true_class' +class Date + def as_json(options = nil) #:nodoc: + if ActiveSupport.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.use_standard_json_time_format + xmlschema + else + strftime('%Y/%m/%d %H:%M:%S %z') + end + end +end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 069842c6c3..4907fae9d6 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -118,6 +118,27 @@ module ActiveSupport end alias_method :iso8601, :xmlschema + # Coerces the date to a string for JSON encoding. + # + # ISO 8601 format is used if ActiveSupport::JSON::Encoding.use_standard_json_time_format is set. + # + # ==== Examples + # + # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true + # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json + # # => "2005-02-01T15:15:10Z" + # + # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false + # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json + # # => "2005/02/01 15:15:10 +0000" + def as_json(options = nil) + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + xmlschema + else + %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) + end + end + def to_yaml(options = {}) if options.kind_of?(YAML::Emitter) utc.to_yaml(options) @@ -301,26 +322,6 @@ module ActiveSupport end private - # Returns a JSON string representing the TimeWithZone. If ActiveSupport.use_standard_json_time_format is set to - # true, the ISO 8601 format is used. - # - # ==== Examples - # - # # With ActiveSupport.use_standard_json_time_format = true - # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json - # # => "2005-02-01T15:15:10Z" - # - # # With ActiveSupport.use_standard_json_time_format = false - # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json - # # => "2005/02/01 15:15:10 +0000" - def rails_to_json(*) - if !ActiveSupport.respond_to?(:use_standard_json_time_format) || ActiveSupport.use_standard_json_time_format - xmlschema.inspect - else - %("#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}") - end - end - def get_period_and_ensure_valid_local_time # we don't want a Time.local instance enforcing its own DST rules as well, # so transfer time values to a utc constructor if necessary |