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*

* 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]

* 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
def blank?
def blank? #:nodoc:
if respond_to?(:empty?) && respond_to?(:strip)
empty? or strip.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:
def remove_subclasses_of(*superclasses)
class Object
def remove_subclasses_of(*superclasses) #:nodoc:
Class.remove_class(*subclasses_of(*superclasses))
end

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

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

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

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

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

unless defined? instance_exec # 1.9
def instance_exec(*arguments, &block)
def instance_exec(*arguments, &block) #:nodoc:
block.bind(self)[*arguments]
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)
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
# 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
# "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.
def acts_like?(duck)
respond_to? :"acts_like_#{duck}?"
respond_to? "acts_like_#{duck}?"
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

class Object #:nodoc:
class Object

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) }
rescue Exception => exception # errors from loading file
exception.blame_file! file
raise
end

def require(file, *extras)
def require(file, *extras) #:nodoc:
Dependencies.new_constants_in(Object) { super(file, *extras) }
rescue Exception => exception # errors from required 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 JSON #:nodoc:
class CircularReferenceError < StandardError #:nodoc:
end

# A string that returns itself as as its JSON-encoded form.
class Variable < String #:nodoc:
def to_json
self
end
end

# When +true+, Hash#to_json will omit quoting string or symbol keys
# if the keys are valid JavaScript identifiers. Note that this is
# technically improper JSON (all object keys must be quoted), so if
# you need strict JSON compliance, set this option to +false+.
mattr_accessor :unquote_hash_key_identifiers
@@unquote_hash_key_identifiers = true
module JSON
RESERVED_WORDS = %w(
abstract delete goto private transient
boolean do if protected try
break double implements public typeof
byte else import return var
case enum in short void
catch export instanceof static volatile
char extends int super while
class final interface switch with
const finally long synchronized
continue float native this
debugger for new throw
default function package throws
) #:nodoc:

class << self
REFERENCE_STACK_VARIABLE = :json_reference_stack

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

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

def reserved_word?(key) #:nodoc:
RESERVED_WORDS.include?(key.to_s)
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
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.