diff options
author | Emilio Tagua <miloops@gmail.com> | 2009-06-09 10:29:55 -0300 |
---|---|---|
committer | Emilio Tagua <miloops@gmail.com> | 2009-06-09 10:29:55 -0300 |
commit | 103b282130dd340143654801430aed787da4c9c6 (patch) | |
tree | eddbe800d02f1a3c23f6266b808e74656af31f82 /activesupport/lib/active_support/json/encoding.rb | |
parent | fd3c55f09fdfb45c33a5383af2c0b9ddf8f63e90 (diff) | |
parent | a94e7d7897a300a95d5d5a00c5efc573b42bcb58 (diff) | |
download | rails-103b282130dd340143654801430aed787da4c9c6.tar.gz rails-103b282130dd340143654801430aed787da4c9c6.tar.bz2 rails-103b282130dd340143654801430aed787da4c9c6.zip |
Merge commit 'rails/master'
Diffstat (limited to 'activesupport/lib/active_support/json/encoding.rb')
-rw-r--r-- | activesupport/lib/active_support/json/encoding.rb | 230 |
1 files changed, 206 insertions, 24 deletions
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 5fefe5b88b..907094a747 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -1,33 +1,215 @@ +# encoding: binary +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) + string = string.dup.force_encoding(::Encoding::BINARY) if string.respond_to?(:force_encoding) + json = '"' + string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] } + 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 |