From 3c4c6bd0df598f865f49a983b4c65c415af4bcfc Mon Sep 17 00:00:00 2001 From: rick Date: Thu, 23 Apr 2009 00:08:40 -0700 Subject: * Add pluggable JSON backends with support for the JSON gem. [rick] Example: ActiveSupport::JSON.backend = "JSONGem" All internal Rails JSON encoding is now handled by ActiveSupport::JSON.encode(). Use of #to_json is not recommended, as it may clash with other libraries that overwrite it. However, you can recover Rails specific functionality if you really want to use #to_json. gem 'json' ActiveSupport::JSON.backend = "JSONGem" class ActiveRecord::Base alias to_json rails_to_json end --- activesupport/lib/active_support/json.rb | 34 ++++++++- .../lib/active_support/json/backends/jsongem.rb | 36 ++++++++++ .../lib/active_support/json/backends/yaml.rb | 83 ++++++++++++++++++++++ activesupport/lib/active_support/json/decoding.rb | 82 --------------------- .../lib/active_support/json/encoders/date.rb | 4 +- .../lib/active_support/json/encoders/date_time.rb | 4 +- .../lib/active_support/json/encoders/enumerable.rb | 6 +- .../active_support/json/encoders/false_class.rb | 4 +- .../lib/active_support/json/encoders/hash.rb | 8 ++- .../lib/active_support/json/encoders/nil_class.rb | 4 +- .../lib/active_support/json/encoders/numeric.rb | 4 +- .../lib/active_support/json/encoders/object.rb | 4 +- .../lib/active_support/json/encoders/regexp.rb | 4 +- .../lib/active_support/json/encoders/string.rb | 4 +- .../lib/active_support/json/encoders/symbol.rb | 4 +- .../lib/active_support/json/encoders/time.rb | 4 +- .../lib/active_support/json/encoders/true_class.rb | 4 +- activesupport/lib/active_support/json/encoding.rb | 2 +- activesupport/lib/active_support/json/variable.rb | 2 +- activesupport/lib/active_support/time_with_zone.rb | 4 +- 20 files changed, 198 insertions(+), 103 deletions(-) create mode 100644 activesupport/lib/active_support/json/backends/jsongem.rb create mode 100644 activesupport/lib/active_support/json/backends/yaml.rb delete mode 100644 activesupport/lib/active_support/json/decoding.rb (limited to 'activesupport/lib/active_support') diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb index 91f19f8a70..fc433de582 100644 --- a/activesupport/lib/active_support/json.rb +++ b/activesupport/lib/active_support/json.rb @@ -1,8 +1,37 @@ +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})?))$/ + + class << self + attr_reader :backend + delegate :decode, :to => :backend + + 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 def escape_html_entities_in_json @@ -19,7 +48,8 @@ module ActiveSupport @escape_html_entities_in_json = value end end + + JSON.backend = 'Yaml' end -require 'active_support/json/encoding' -require 'active_support/json/decoding' +require 'active_support/json/encoding' \ No newline at end of file diff --git a/activesupport/lib/active_support/json/backends/jsongem.rb b/activesupport/lib/active_support/json/backends/jsongem.rb new file mode 100644 index 0000000000..de847e30a3 --- /dev/null +++ b/activesupport/lib/active_support/json/backends/jsongem.rb @@ -0,0 +1,36 @@ +module ActiveSupport + module JSON + ParseError = ::JSON::ParserError + + module Backends + module JSONGem + extend self + + # Converts a JSON string into a Ruby object. + def decode(json) + data = ::JSON.parse(json) + if ActiveSupport.parse_json_times + convert_dates_from(data) + else + data + end + end + + private + def convert_dates_from(data) + case data + when DATE_REGEX + DateTime.parse(data) + when Array + data.map! { |d| convert_dates_from(d) } + when Hash + data.each do |key, value| + data[key] = convert_dates_from(value) + end + else data + end + end + end + end + end +end \ No newline at end of file diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb new file mode 100644 index 0000000000..c7db508c23 --- /dev/null +++ b/activesupport/lib/active_support/json/backends/yaml.rb @@ -0,0 +1,83 @@ +require 'active_support/core_ext/string/starts_ends_with' + +module ActiveSupport + module JSON + class ParseError < StandardError + end + + module Backends + module Yaml + extend self + + # Converts a JSON string into a Ruby object. + def decode(json) + YAML.load(convert_json_to_yaml(json)) + rescue ArgumentError => e + raise ParseError, "Invalid JSON string" + end + + protected + # Ensure that ":" and "," are always followed by a space + def convert_json_to_yaml(json) #:nodoc: + require 'strscan' unless defined? ::StringScanner + scanner, quoting, marks, pos, times = ::StringScanner.new(json), false, [], nil, [] + while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/) + case char = scanner[1] + when '"', "'" + if !quoting + quoting = char + pos = scanner.pos + elsif quoting == char + if json[pos..scanner.pos-2] =~ DATE_REGEX + # found a date, track the exact positions of the quotes so we can remove them later. + # oh, and increment them for each current mark, each one is an extra padded space that bumps + # the position in the final YAML output + total_marks = marks.size + times << pos+total_marks << scanner.pos+total_marks + end + quoting = false + end + when ":","," + marks << scanner.pos - 1 unless quoting + end + end + + if marks.empty? + json.gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do + ustr = $1 + if ustr.start_with?('u') + [ustr[1..-1].to_i(16)].pack("U") + elsif ustr == '\\' + '\\\\' + else + ustr + end + end + else + left_pos = [-1].push(*marks) + right_pos = marks << scanner.pos + scanner.rest_size + output = [] + left_pos.each_with_index do |left, i| + scanner.pos = left.succ + output << scanner.peek(right_pos[i] - scanner.pos + 1).gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do + ustr = $1 + if ustr.start_with?('u') + [ustr[1..-1].to_i(16)].pack("U") + elsif ustr == '\\' + '\\\\' + else + ustr + end + end + end + output = output * " " + + times.each { |i| output[i-1] = ' ' } + output.gsub!(/\\\//, '/') + output + end + end + end + end + end +end diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb deleted file mode 100644 index 70e9f40fc7..0000000000 --- a/activesupport/lib/active_support/json/decoding.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'active_support/core_ext/string/starts_ends_with' - -module ActiveSupport - module JSON - class ParseError < StandardError - end - - class << self - # Converts a JSON string into a Ruby object. - def decode(json) - YAML.load(convert_json_to_yaml(json)) - rescue ArgumentError => e - raise ParseError, "Invalid JSON string" - end - - protected - # 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})?)?)$/ - - # Ensure that ":" and "," are always followed by a space - def convert_json_to_yaml(json) #:nodoc: - require 'strscan' unless defined? ::StringScanner - scanner, quoting, marks, pos, times = ::StringScanner.new(json), false, [], nil, [] - while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/) - case char = scanner[1] - when '"', "'" - if !quoting - quoting = char - pos = scanner.pos - elsif quoting == char - if json[pos..scanner.pos-2] =~ DATE_REGEX - # found a date, track the exact positions of the quotes so we can remove them later. - # oh, and increment them for each current mark, each one is an extra padded space that bumps - # the position in the final YAML output - total_marks = marks.size - times << pos+total_marks << scanner.pos+total_marks - end - quoting = false - end - when ":","," - marks << scanner.pos - 1 unless quoting - end - end - - if marks.empty? - json.gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do - ustr = $1 - if ustr.start_with?('u') - [ustr[1..-1].to_i(16)].pack("U") - elsif ustr == '\\' - '\\\\' - else - ustr - end - end - else - left_pos = [-1].push(*marks) - right_pos = marks << scanner.pos + scanner.rest_size - output = [] - left_pos.each_with_index do |left, i| - scanner.pos = left.succ - output << scanner.peek(right_pos[i] - scanner.pos + 1).gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do - ustr = $1 - if ustr.start_with?('u') - [ustr[1..-1].to_i(16)].pack("U") - elsif ustr == '\\' - '\\\\' - else - ustr - end - end - end - output = output * " " - - times.each { |i| output[i-1] = ' ' } - output.gsub!(/\\\//, '/') - output - end - end - end - end -end diff --git a/activesupport/lib/active_support/json/encoders/date.rb b/activesupport/lib/active_support/json/encoders/date.rb index cc84de1388..79c3957362 100644 --- a/activesupport/lib/active_support/json/encoders/date.rb +++ b/activesupport/lib/active_support/json/encoders/date.rb @@ -11,11 +11,13 @@ class Date # # With ActiveSupport.use_standard_json_time_format = false # Date.new(2005,2,1).to_json # # => "2005/02/01" - def to_json(options = nil) + def rails_to_json(options = nil) if ActiveSupport.use_standard_json_time_format %("#{strftime("%Y-%m-%d")}") else %("#{strftime("%Y/%m/%d")}") end end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/date_time.rb b/activesupport/lib/active_support/json/encoders/date_time.rb index 6c85824105..cdfc39b9f3 100644 --- a/activesupport/lib/active_support/json/encoders/date_time.rb +++ b/activesupport/lib/active_support/json/encoders/date_time.rb @@ -11,11 +11,13 @@ class DateTime # # 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 to_json(options = nil) + def rails_to_json(options = nil) if ActiveSupport.use_standard_json_time_format xmlschema.inspect else strftime('"%Y/%m/%d %H:%M:%S %z"') end end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/enumerable.rb b/activesupport/lib/active_support/json/encoders/enumerable.rb index 881b1d62c1..e1c3ec249d 100644 --- a/activesupport/lib/active_support/json/encoders/enumerable.rb +++ b/activesupport/lib/active_support/json/encoders/enumerable.rb @@ -6,7 +6,9 @@ module Enumerable # # => users.to_json(:only => :name) # # will pass the :only => :name option to each user. - def to_json(options = {}) #:nodoc: - "[#{map { |value| ActiveSupport::JSON.encode(value, options) } * ', '}]" + def rails_to_json(options = {}) #:nodoc: + "[#{map { |value| ActiveSupport::JSON.encode(value, options) } * ','}]" end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/false_class.rb b/activesupport/lib/active_support/json/encoders/false_class.rb index bf0844334b..a7657cca37 100644 --- a/activesupport/lib/active_support/json/encoders/false_class.rb +++ b/activesupport/lib/active_support/json/encoders/false_class.rb @@ -1,5 +1,7 @@ class FalseClass - def to_json(options = nil) #:nodoc: + def rails_to_json(options = nil) #:nodoc: 'false' end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/hash.rb b/activesupport/lib/active_support/json/encoders/hash.rb index d87b880743..19b97d7b8c 100644 --- a/activesupport/lib/active_support/json/encoders/hash.rb +++ b/activesupport/lib/active_support/json/encoders/hash.rb @@ -30,7 +30,7 @@ class Hash # would pass the :include => :posts option to users, # allowing the posts association in the User model to be converted to JSON # as well. - def to_json(options = {}) #:nodoc: + def rails_to_json(options = {}) #:nodoc: hash_keys = self.keys if except = options[:except] @@ -41,8 +41,10 @@ class Hash result = '{' result << hash_keys.map do |key| - "#{ActiveSupport::JSON.encode(key.to_s)}: #{ActiveSupport::JSON.encode(self[key], options)}" - end * ', ' + "#{ActiveSupport::JSON.encode(key.to_s)}:#{ActiveSupport::JSON.encode(self[key], options)}" + end * ',' result << '}' end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/nil_class.rb b/activesupport/lib/active_support/json/encoders/nil_class.rb index 4763471ac4..b31e1dd249 100644 --- a/activesupport/lib/active_support/json/encoders/nil_class.rb +++ b/activesupport/lib/active_support/json/encoders/nil_class.rb @@ -1,5 +1,7 @@ class NilClass - def to_json(options = nil) #:nodoc: + def rails_to_json(options = nil) #:nodoc: 'null' end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/numeric.rb b/activesupport/lib/active_support/json/encoders/numeric.rb index 38713fb369..491b330c39 100644 --- a/activesupport/lib/active_support/json/encoders/numeric.rb +++ b/activesupport/lib/active_support/json/encoders/numeric.rb @@ -1,5 +1,7 @@ class Numeric - def to_json(options = nil) #:nodoc: + def rails_to_json(options = nil) #:nodoc: to_s end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/object.rb b/activesupport/lib/active_support/json/encoders/object.rb index 0475967aee..d68f50562e 100644 --- a/activesupport/lib/active_support/json/encoders/object.rb +++ b/activesupport/lib/active_support/json/encoders/object.rb @@ -2,7 +2,9 @@ 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 = {}) + def rails_to_json(options = {}) ActiveSupport::JSON.encode(instance_values, options) end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/regexp.rb b/activesupport/lib/active_support/json/encoders/regexp.rb index b6116b70b5..63ccd7c490 100644 --- a/activesupport/lib/active_support/json/encoders/regexp.rb +++ b/activesupport/lib/active_support/json/encoders/regexp.rb @@ -1,5 +1,7 @@ class Regexp - def to_json(options = nil) #:nodoc: + def rails_to_json(options = nil) #:nodoc: inspect end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/string.rb b/activesupport/lib/active_support/json/encoders/string.rb index 5ef797955a..27bef3b9cc 100644 --- a/activesupport/lib/active_support/json/encoders/string.rb +++ b/activesupport/lib/active_support/json/encoders/string.rb @@ -22,7 +22,7 @@ end ActiveSupport.escape_html_entities_in_json = true class String - def to_json(options = nil) #:nodoc: + def rails_to_json(options = nil) #:nodoc: json = '"' + gsub(ActiveSupport::JSON::Encoding.escape_regex) { |s| ActiveSupport::JSON::Encoding::ESCAPED_CHARS[s] } @@ -33,4 +33,6 @@ class String s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/, '\\\\u\&') } + '"' end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/symbol.rb b/activesupport/lib/active_support/json/encoders/symbol.rb index 485112f97c..6487bf8cb7 100644 --- a/activesupport/lib/active_support/json/encoders/symbol.rb +++ b/activesupport/lib/active_support/json/encoders/symbol.rb @@ -1,5 +1,7 @@ class Symbol - def to_json(options = {}) #:nodoc: + def rails_to_json(options = {}) #:nodoc: ActiveSupport::JSON.encode(to_s, options) end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/time.rb b/activesupport/lib/active_support/json/encoders/time.rb index 7d97c38d77..2699672949 100644 --- a/activesupport/lib/active_support/json/encoders/time.rb +++ b/activesupport/lib/active_support/json/encoders/time.rb @@ -13,11 +13,13 @@ class Time # # 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 to_json(options = nil) + def rails_to_json(options = nil) if ActiveSupport.use_standard_json_time_format xmlschema.inspect else %("#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}") end end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoders/true_class.rb b/activesupport/lib/active_support/json/encoders/true_class.rb index 037d812b3f..ac7c7d1e87 100644 --- a/activesupport/lib/active_support/json/encoders/true_class.rb +++ b/activesupport/lib/active_support/json/encoders/true_class.rb @@ -1,5 +1,7 @@ class TrueClass - def to_json(options = nil) #:nodoc: + def rails_to_json(options = nil) #:nodoc: 'true' end + + alias to_json rails_to_json end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index aaaa3cdfd2..42a217cedc 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -8,7 +8,7 @@ module ActiveSupport seen = (options[:seen] ||= []) raise CircularReferenceError, 'object references itself' if seen.include?(value) seen << value - value.send(:to_json, options) + value.send(:rails_to_json, options) ensure seen.pop end diff --git a/activesupport/lib/active_support/json/variable.rb b/activesupport/lib/active_support/json/variable.rb index 7fd23b0a9e..3ee152ee3c 100644 --- a/activesupport/lib/active_support/json/variable.rb +++ b/activesupport/lib/active_support/json/variable.rb @@ -2,7 +2,7 @@ module ActiveSupport module JSON # A string that returns itself as its JSON-encoded form. class Variable < String - def to_json(options=nil) + def rails_to_json(options=nil) self end end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 0d4c75d272..1949ce0ad3 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -130,7 +130,7 @@ module ActiveSupport # # 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 to_json(options = nil) + def rails_to_json(options = nil) if !ActiveSupport.respond_to?(:use_standard_json_time_format) || ActiveSupport.use_standard_json_time_format xmlschema.inspect else @@ -138,6 +138,8 @@ module ActiveSupport end end + alias to_json rails_to_json + def to_yaml(options = {}) if options.kind_of?(YAML::Emitter) utc.to_yaml(options) -- cgit v1.2.3