From 00ee990443189649e481b2c30945e7a1029d8280 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 5 Jun 2009 18:25:07 -0700 Subject: [PATCH] JSON: split encoding and coercion --- activeresource/lib/active_resource/base.rb | 11 +- activesupport/CHANGELOG | 2 + activesupport/lib/active_support/json.rb | 87 +------ .../lib/active_support/json/decoding.rb | 35 +++ .../lib/active_support/json/encoders/date.rb | 22 -- .../active_support/json/encoders/date_time.rb | 22 -- .../json/encoders/enumerable.rb | 13 - .../json/encoders/false_class.rb | 6 - .../lib/active_support/json/encoders/hash.rb | 51 ---- .../active_support/json/encoders/nil_class.rb | 6 - .../active_support/json/encoders/numeric.rb | 6 - .../active_support/json/encoders/object.rb | 13 - .../active_support/json/encoders/regexp.rb | 6 - .../active_support/json/encoders/string.rb | 6 - .../active_support/json/encoders/symbol.rb | 6 - .../lib/active_support/json/encoders/time.rb | 24 -- .../json/encoders/true_class.rb | 6 - .../lib/active_support/json/encoding.rb | 229 ++++++++++++++++-- .../lib/active_support/time_with_zone.rb | 41 ++-- activesupport/test/json/encoding_test.rb | 19 +- 20 files changed, 279 insertions(+), 332 deletions(-) create mode 100644 activesupport/lib/active_support/json/decoding.rb delete mode 100644 activesupport/lib/active_support/json/encoders/date.rb delete mode 100644 activesupport/lib/active_support/json/encoders/date_time.rb delete mode 100644 activesupport/lib/active_support/json/encoders/enumerable.rb delete mode 100644 activesupport/lib/active_support/json/encoders/false_class.rb delete mode 100644 activesupport/lib/active_support/json/encoders/hash.rb delete mode 100644 activesupport/lib/active_support/json/encoders/nil_class.rb delete mode 100644 activesupport/lib/active_support/json/encoders/numeric.rb delete mode 100644 activesupport/lib/active_support/json/encoders/object.rb delete mode 100644 activesupport/lib/active_support/json/encoders/regexp.rb delete mode 100644 activesupport/lib/active_support/json/encoders/string.rb delete mode 100644 activesupport/lib/active_support/json/encoders/symbol.rb delete mode 100644 activesupport/lib/active_support/json/encoders/time.rb delete mode 100644 activesupport/lib/active_support/json/encoders/true_class.rb diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 11a7bbba3ec90..dbc418bef3628 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -873,7 +873,7 @@ def to_xml(options={}) attributes.to_xml({:root => self.class.element_name}.merge(options)) end - # Converts the resource to a JSON string representation. + # Coerces to a hash for JSON encoding. # # ==== Options # The +options+ are passed to the +to_json+ method on each @@ -897,8 +897,8 @@ def to_xml(options={}) # # person.to_json(:except => ["first_name"]) # # => {"last_name": "Smith"} - def to_json(options={}) - ActiveSupport::JSON.encode(attributes, options) + def as_json(options = nil) + attributes.as_json(options) end # Returns the serialized string representation of the resource in the configured @@ -1072,11 +1072,6 @@ def split_options(options = {}) self.class.__send__(:split_options, options) end - # For compatibility with ActiveSupport::JSON.encode - def rails_to_json(options, *args) - to_json(options) - end - def method_missing(method_symbol, *arguments) #:nodoc: method_name = method_symbol.to_s diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 93e0f1aa24be6..9c5803c52ac84 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* JSON: +Object#to_json+ calls +as_json+ to coerce itself into something natively encodable like +Hash+, +Integer+, or +String+. Override +as_json+ instead of +to_json+ so you're JSON library agnostic. [Jeremy Kemper] + * String #to_time and #to_datetime: handle fractional seconds #864 [Jason Frey] * Update bundled TZInfo to v0.3.13 [Geoff Buesing] diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb index 6d845182fb494..3e1d9b1d3361b 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 0000000000000..5d4caa362fca6 --- /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 9adb3c20e270a..0000000000000 --- 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 3a29292b24f9d..0000000000000 --- 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 898990a59c741..0000000000000 --- 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 :only => :name 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 eb975fe542e06..0000000000000 --- 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 477148484303e..0000000000000 --- 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 :only and :except 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 to_json method. For example: - # - # users = User.find(:all) - # { :users => users, :count => users.size }.to_json(:include => :posts) - # - # would pass the :include => :posts option to users, - # 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 8c51dba38458a..0000000000000 --- 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 c7cd0df1d7991..0000000000000 --- 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 9cc12d91acb13..0000000000000 --- 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 ee42db4d02bf0..0000000000000 --- 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 4a6b21c1c0c38..0000000000000 --- 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 d575350a4ec09..0000000000000 --- 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 d434b9aacef45..0000000000000 --- 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 bc25a6db78cc3..0000000000000 --- 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 5fefe5b88b13a..97e573f7a63e6 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 069842c6c375d..4907fae9d6801 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -118,6 +118,27 @@ def xmlschema(fraction_digits = 0) 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 @@ def method_missing(sym, *args, &block) 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 diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 1a0e6d543c6a7..983235d06c249 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -9,6 +9,12 @@ def initialize(a, b) end end + class Custom + def as_json(options) + 'custom' + end + end + TrueTests = [[ true, %(true) ]] FalseTests = [[ false, %(false) ]] NilTests = [[ nil, %(null) ]] @@ -27,6 +33,7 @@ def initialize(a, b) [ :"a b", %("a b") ]] ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]] + CustomTests = [[ Custom.new, '"custom"' ]] VariableTests = [[ ActiveSupport::JSON::Variable.new('foo'), 'foo'], [ ActiveSupport::JSON::Variable.new('alert("foo")'), 'alert("foo")']] @@ -76,7 +83,7 @@ def test_utf8_string_encoded_properly_when_kcode_is_utf8 def test_exception_raised_when_encoding_circular_reference a = [1] a << a - assert_raise(ActiveSupport::JSON::CircularReferenceError) { ActiveSupport::JSON.encode(a) } + assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } end def test_hash_key_identifiers_are_always_quoted @@ -129,11 +136,9 @@ def with_env_tz(new_tz = 'US/Eastern') class JsonOptionsTests < Test::Unit::TestCase def test_enumerable_should_passthrough_options_to_elements - json_options = { :include => :posts } - ActiveSupport::JSON.expects(:encode).with(1, json_options) - ActiveSupport::JSON.expects(:encode).with(2, json_options) - ActiveSupport::JSON.expects(:encode).with('foo', json_options) - - [1, 2, 'foo'].send(:rails_to_json, json_options) + value, options = Object.new, Object.new + def value.as_json(options) options end + def options.encode_json(encoder) self end + assert_equal options, ActiveSupport::JSON.encode(value, options) end end