Navigation Menu

Skip to content

Commit

Permalink
Refactor ActiveSupport::JSON to be less obtuse. Add support for JSON …
Browse files Browse the repository at this point in the history
…decoding by way of Syck with ActiveSupport::JSON.decode(json_string). Prevent hash keys that are JavaScript reserved words from being unquoted during encoding.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6443 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information
sstephenson committed Mar 18, 2007
1 parent 3d5c947 commit 3202fba
Show file tree
Hide file tree
Showing 24 changed files with 265 additions and 166 deletions.
2 changes: 2 additions & 0 deletions activesupport/CHANGELOG
@@ -1,5 +1,7 @@
*SVN* *SVN*


* Refactor ActiveSupport::JSON to be less obtuse. Add support for JSON decoding by way of Syck with ActiveSupport::JSON.decode(json_string). Prevent hash keys that are JavaScript reserved words from being unquoted during encoding. [Sam Stephenson]

* alias_method_chain preserves the original method's visibility. #7854 [Jonathan Viney] * alias_method_chain preserves the original method's visibility. #7854 [Jonathan Viney]


* Update Dependencies to ignore constants inherited from ancestors. Closes #6951. [Nicholas Seckar] * Update Dependencies to ignore constants inherited from ancestors. Closes #6951. [Nicholas Seckar]
Expand Down
4 changes: 2 additions & 2 deletions activesupport/lib/active_support/core_ext/blank.rb
@@ -1,6 +1,6 @@
class Object #:nodoc: class Object
# "", " ", nil, [], and {} are blank # "", " ", nil, [], and {} are blank
def blank? def blank? #:nodoc:
if respond_to?(:empty?) && respond_to?(:strip) if respond_to?(:empty?) && respond_to?(:strip)
empty? or strip.empty? empty? or strip.empty?
elsif respond_to?(:empty?) elsif respond_to?(:empty?)
Expand Down
16 changes: 8 additions & 8 deletions activesupport/lib/active_support/core_ext/object/extending.rb
@@ -1,9 +1,9 @@
class Object #:nodoc: class Object
def remove_subclasses_of(*superclasses) def remove_subclasses_of(*superclasses) #:nodoc:
Class.remove_class(*subclasses_of(*superclasses)) Class.remove_class(*subclasses_of(*superclasses))
end end


def subclasses_of(*superclasses) def subclasses_of(*superclasses) #:nodoc:
subclasses = [] subclasses = []
ObjectSpace.each_object(Class) do |k| ObjectSpace.each_object(Class) do |k|
next unless # Exclude this class unless next unless # Exclude this class unless
Expand All @@ -16,31 +16,31 @@ def subclasses_of(*superclasses)
subclasses subclasses
end end


def extended_by def extended_by #:nodoc:
ancestors = class << self; ancestors end ancestors = class << self; ancestors end
ancestors.select { |mod| mod.class == Module } - [ Object, Kernel ] ancestors.select { |mod| mod.class == Module } - [ Object, Kernel ]
end end


def copy_instance_variables_from(object, exclude = []) def copy_instance_variables_from(object, exclude = []) #:nodoc:
exclude += object.protected_instance_variables if object.respond_to? :protected_instance_variables exclude += object.protected_instance_variables if object.respond_to? :protected_instance_variables


instance_variables = object.instance_variables - exclude.map { |name| name.to_s } instance_variables = object.instance_variables - exclude.map { |name| name.to_s }
instance_variables.each { |name| instance_variable_set(name, object.instance_variable_get(name)) } instance_variables.each { |name| instance_variable_set(name, object.instance_variable_get(name)) }
end end


def extend_with_included_modules_from(object) def extend_with_included_modules_from(object) #:nodoc:
object.extended_by.each { |mod| extend mod } object.extended_by.each { |mod| extend mod }
end end


def instance_values def instance_values #:nodoc:
instance_variables.inject({}) do |values, name| instance_variables.inject({}) do |values, name|
values[name[1..-1]] = instance_variable_get(name) values[name[1..-1]] = instance_variable_get(name)
values values
end end
end end


unless defined? instance_exec # 1.9 unless defined? instance_exec # 1.9
def instance_exec(*arguments, &block) def instance_exec(*arguments, &block) #:nodoc:
block.bind(self)[*arguments] block.bind(self)[*arguments]
end end
end end
Expand Down
11 changes: 1 addition & 10 deletions activesupport/lib/active_support/core_ext/object/misc.rb
Expand Up @@ -43,21 +43,12 @@ def with_options(options)
yield ActiveSupport::OptionMerger.new(self, options) yield ActiveSupport::OptionMerger.new(self, options)
end end


# Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
#
# Account.find(1).to_json
# => "{attributes: {username: \"foo\", id: \"1\", password: \"bar\"}}"
#
def to_json
ActiveSupport::JSON.encode(self)
end

# A duck-type assistant method. For example, ActiveSupport extends Date # A duck-type assistant method. For example, ActiveSupport extends Date
# to define an acts_like_date? method, and extends Time to define # to define an acts_like_date? method, and extends Time to define
# acts_like_time?. As a result, we can do "x.acts_like?(:time)" and # acts_like_time?. As a result, we can do "x.acts_like?(:time)" and
# "x.acts_like?(:date)" to do duck-type-safe comparisons, since classes that # "x.acts_like?(:date)" to do duck-type-safe comparisons, since classes that
# we want to act like Time simply need to define an acts_like_time? method. # we want to act like Time simply need to define an acts_like_time? method.
def acts_like?(duck) def acts_like?(duck)
respond_to? :"acts_like_#{duck}?" respond_to? "acts_like_#{duck}?"
end end
end end
6 changes: 3 additions & 3 deletions activesupport/lib/active_support/dependencies.rb
Expand Up @@ -480,18 +480,18 @@ def const_missing(class_id)
end end
end end


class Object #:nodoc: class Object


alias_method :load_without_new_constant_marking, :load alias_method :load_without_new_constant_marking, :load


def load(file, *extras) def load(file, *extras) #:nodoc:
Dependencies.new_constants_in(Object) { super(file, *extras) } Dependencies.new_constants_in(Object) { super(file, *extras) }
rescue Exception => exception # errors from loading file rescue Exception => exception # errors from loading file
exception.blame_file! file exception.blame_file! file
raise raise
end end


def require(file, *extras) def require(file, *extras) #:nodoc:
Dependencies.new_constants_in(Object) { super(file, *extras) } Dependencies.new_constants_in(Object) { super(file, *extras) }
rescue Exception => exception # errors from required file rescue Exception => exception # errors from required file
exception.blame_file! file exception.blame_file! file
Expand Down
61 changes: 22 additions & 39 deletions activesupport/lib/active_support/json.rb
@@ -1,48 +1,31 @@
require 'active_support/json/encoders' require 'active_support/json/encoding'
require 'active_support/json/decoding'


module ActiveSupport module ActiveSupport
module JSON #:nodoc: module JSON
class CircularReferenceError < StandardError #:nodoc: RESERVED_WORDS = %w(
end abstract delete goto private transient

boolean do if protected try
# A string that returns itself as as its JSON-encoded form. break double implements public typeof
class Variable < String #:nodoc: byte else import return var
def to_json case enum in short void
self catch export instanceof static volatile
end char extends int super while
end class final interface switch with

const finally long synchronized
# When +true+, Hash#to_json will omit quoting string or symbol keys continue float native this
# if the keys are valid JavaScript identifiers. Note that this is debugger for new throw
# technically improper JSON (all object keys must be quoted), so if default function package throws
# you need strict JSON compliance, set this option to +false+. ) #:nodoc:
mattr_accessor :unquote_hash_key_identifiers
@@unquote_hash_key_identifiers = true


class << self class << self
REFERENCE_STACK_VARIABLE = :json_reference_stack def valid_identifier?(key) #:nodoc:

key.to_s =~ /^[[:alpha:]_$][[:alnum:]_$]*$/ && !reserved_word?(key)
def encode(value)
raise_on_circular_reference(value) do
Encoders[value.class].call(value)
end
end end


def can_unquote_identifier?(key) def reserved_word?(key) #:nodoc:
return false unless unquote_hash_key_identifiers RESERVED_WORDS.include?(key.to_s)
key.to_s =~ /^[[:alpha:]_$][[:alnum:]_$]*$/
end end

protected
def raise_on_circular_reference(value)
stack = Thread.current[REFERENCE_STACK_VARIABLE] ||= []
raise CircularReferenceError, 'object references itself' if
stack.include? value
stack << value
yield
ensure
stack.pop
end
end end
end end
end end
40 changes: 40 additions & 0 deletions activesupport/lib/active_support/json/decoding.rb
@@ -0,0 +1,40 @@
require 'yaml'
require 'strscan'

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
# Ensure that ":" and "," are always followed by a space
def convert_json_to_yaml(json) #:nodoc:
scanner, quoting, marks = StringScanner.new(json), false, []

while scanner.scan_until(/(['":,]|\\.)/)
case char = scanner[1]
when '"', "'"
quoting = quoting == char ? false : char
when ":", ","
marks << scanner.pos - 1 unless quoting
end
end

if marks.empty?
json
else
ranges = ([0] + marks.map(&:succ)).zip(marks + [json.length])
ranges.map { |(left, right)| json[left..right] }.join(" ")
end
end
end
end
end
25 changes: 0 additions & 25 deletions activesupport/lib/active_support/json/encoders.rb

This file was deleted.

68 changes: 0 additions & 68 deletions activesupport/lib/active_support/json/encoders/core.rb

This file was deleted.

5 changes: 5 additions & 0 deletions activesupport/lib/active_support/json/encoders/enumerable.rb
@@ -0,0 +1,5 @@
module Enumerable
def to_json #:nodoc:
"[#{map { |value| ActiveSupport::JSON.encode(value) } * ', '}]"
end
end
5 changes: 5 additions & 0 deletions activesupport/lib/active_support/json/encoders/false_class.rb
@@ -0,0 +1,5 @@
class FalseClass
def to_json #:nodoc:
'false'
end
end
12 changes: 12 additions & 0 deletions activesupport/lib/active_support/json/encoders/hash.rb
@@ -0,0 +1,12 @@
class Hash
def to_json #:nodoc:
returning result = '{' do
result << map do |key, value|
key = ActiveSupport::JSON::Variable.new(key.to_s) if
ActiveSupport::JSON.can_unquote_identifier?(key)
"#{ActiveSupport::JSON.encode(key)}: #{ActiveSupport::JSON.encode(value)}"
end * ', '
result << '}'
end
end
end
5 changes: 5 additions & 0 deletions activesupport/lib/active_support/json/encoders/nil_class.rb
@@ -0,0 +1,5 @@
class NilClass
def to_json #:nodoc:
'null'
end
end
5 changes: 5 additions & 0 deletions activesupport/lib/active_support/json/encoders/numeric.rb
@@ -0,0 +1,5 @@
class Numeric
def to_json #:nodoc:
to_s
end
end
10 changes: 10 additions & 0 deletions activesupport/lib/active_support/json/encoders/object.rb
@@ -0,0 +1,10 @@
class Object
# Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
#
# Account.find(1).to_json
# => "{attributes: {username: \"foo\", id: \"1\", password: \"bar\"}}"
#
def to_json
ActiveSupport::JSON.encode(instance_values)
end
end
5 changes: 5 additions & 0 deletions activesupport/lib/active_support/json/encoders/regexp.rb
@@ -0,0 +1,5 @@
class Regexp
def to_json #:nodoc:
inspect
end
end

0 comments on commit 3202fba

Please sign in to comment.