aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/json
diff options
context:
space:
mode:
authorJeremy Kemper <jeremy@bitsweat.net>2009-06-05 18:25:07 -0700
committerJeremy Kemper <jeremy@bitsweat.net>2009-06-08 13:21:30 -0700
commit00ee990443189649e481b2c30945e7a1029d8280 (patch)
treec30e731055da3a6f67ea4c154059ff7245ef6e1c /activesupport/lib/active_support/json
parent5e1b46d4c285124737abe2e08dec97e4af1f4be7 (diff)
downloadrails-00ee990443189649e481b2c30945e7a1029d8280.tar.gz
rails-00ee990443189649e481b2c30945e7a1029d8280.tar.bz2
rails-00ee990443189649e481b2c30945e7a1029d8280.zip
JSON: split encoding and coercion
Diffstat (limited to 'activesupport/lib/active_support/json')
-rw-r--r--activesupport/lib/active_support/json/decoding.rb35
-rw-r--r--activesupport/lib/active_support/json/encoders/date.rb22
-rw-r--r--activesupport/lib/active_support/json/encoders/date_time.rb22
-rw-r--r--activesupport/lib/active_support/json/encoders/enumerable.rb13
-rw-r--r--activesupport/lib/active_support/json/encoders/false_class.rb6
-rw-r--r--activesupport/lib/active_support/json/encoders/hash.rb51
-rw-r--r--activesupport/lib/active_support/json/encoders/nil_class.rb6
-rw-r--r--activesupport/lib/active_support/json/encoders/numeric.rb6
-rw-r--r--activesupport/lib/active_support/json/encoders/object.rb13
-rw-r--r--activesupport/lib/active_support/json/encoders/regexp.rb6
-rw-r--r--activesupport/lib/active_support/json/encoders/string.rb6
-rw-r--r--activesupport/lib/active_support/json/encoders/symbol.rb6
-rw-r--r--activesupport/lib/active_support/json/encoders/time.rb24
-rw-r--r--activesupport/lib/active_support/json/encoders/true_class.rb6
-rw-r--r--activesupport/lib/active_support/json/encoding.rb229
15 files changed, 240 insertions, 211 deletions
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