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.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6443 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
3d5c947155
commit
3202fbabe6
|
@ -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]
|
||||||
|
|
|
@ -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?)
|
||||||
|
|
|
@ -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
|
||||||
|
@ -16,23 +16,23 @@ class Object #:nodoc:
|
||||||
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
|
||||||
|
@ -40,7 +40,7 @@ class Object #:nodoc:
|
||||||
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
|
||||||
|
|
|
@ -43,21 +43,12 @@ class Object
|
||||||
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
|
|
@ -480,18 +480,18 @@ class Class
|
||||||
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -1,25 +0,0 @@
|
||||||
module ActiveSupport
|
|
||||||
module JSON #:nodoc:
|
|
||||||
module Encoders
|
|
||||||
mattr_accessor :encoders
|
|
||||||
@@encoders = {}
|
|
||||||
|
|
||||||
class << self
|
|
||||||
def define_encoder(klass, &block)
|
|
||||||
encoders[klass] = block
|
|
||||||
end
|
|
||||||
|
|
||||||
def [](klass)
|
|
||||||
klass.ancestors.each do |k|
|
|
||||||
encoder = encoders[k]
|
|
||||||
return encoder if encoder
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Dir[File.dirname(__FILE__) + '/encoders/*.rb'].each do |file|
|
|
||||||
require file[0..-4]
|
|
||||||
end
|
|
|
@ -1,68 +0,0 @@
|
||||||
module ActiveSupport
|
|
||||||
module JSON #:nodoc:
|
|
||||||
module Encoders #:nodoc:
|
|
||||||
define_encoder Object do |object|
|
|
||||||
object.instance_values.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
define_encoder TrueClass do
|
|
||||||
'true'
|
|
||||||
end
|
|
||||||
|
|
||||||
define_encoder FalseClass do
|
|
||||||
'false'
|
|
||||||
end
|
|
||||||
|
|
||||||
define_encoder NilClass do
|
|
||||||
'null'
|
|
||||||
end
|
|
||||||
|
|
||||||
ESCAPED_CHARS = {
|
|
||||||
"\010" => '\b',
|
|
||||||
"\f" => '\f',
|
|
||||||
"\n" => '\n',
|
|
||||||
"\r" => '\r',
|
|
||||||
"\t" => '\t',
|
|
||||||
'"' => '\"',
|
|
||||||
'\\' => '\\\\'
|
|
||||||
}
|
|
||||||
|
|
||||||
define_encoder String do |string|
|
|
||||||
'"' + string.gsub(/[\010\f\n\r\t"\\]/) { |s|
|
|
||||||
ESCAPED_CHARS[s]
|
|
||||||
}.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
|
|
||||||
|
|
||||||
define_encoder Numeric do |numeric|
|
|
||||||
numeric.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
define_encoder Symbol do |symbol|
|
|
||||||
symbol.to_s.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
define_encoder Enumerable do |enumerable|
|
|
||||||
"[#{enumerable.map { |value| value.to_json } * ', '}]"
|
|
||||||
end
|
|
||||||
|
|
||||||
define_encoder Hash do |hash|
|
|
||||||
returning result = '{' do
|
|
||||||
result << hash.map do |key, value|
|
|
||||||
key = ActiveSupport::JSON::Variable.new(key.to_s) if
|
|
||||||
ActiveSupport::JSON.can_unquote_identifier?(key)
|
|
||||||
"#{key.to_json}: #{value.to_json}"
|
|
||||||
end * ', '
|
|
||||||
result << '}'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
define_encoder Regexp do |regexp|
|
|
||||||
regexp.inspect
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
module Enumerable
|
||||||
|
def to_json #:nodoc:
|
||||||
|
"[#{map { |value| ActiveSupport::JSON.encode(value) } * ', '}]"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class FalseClass
|
||||||
|
def to_json #:nodoc:
|
||||||
|
'false'
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
class NilClass
|
||||||
|
def to_json #:nodoc:
|
||||||
|
'null'
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class Numeric
|
||||||
|
def to_json #:nodoc:
|
||||||
|
to_s
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
class Regexp
|
||||||
|
def to_json #:nodoc:
|
||||||
|
inspect
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,27 @@
|
||||||
|
module ActiveSupport
|
||||||
|
module JSON
|
||||||
|
module Encoding
|
||||||
|
ESCAPED_CHARS = {
|
||||||
|
"\010" => '\b',
|
||||||
|
"\f" => '\f',
|
||||||
|
"\n" => '\n',
|
||||||
|
"\r" => '\r',
|
||||||
|
"\t" => '\t',
|
||||||
|
'"' => '\"',
|
||||||
|
'\\' => '\\\\'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class String
|
||||||
|
def to_json #:nodoc:
|
||||||
|
'"' + gsub(/[\010\f\n\r\t"\\]/) { |s|
|
||||||
|
ActiveSupport::JSON::Encoding::ESCAPED_CHARS[s]
|
||||||
|
}.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
|
|
@ -0,0 +1,5 @@
|
||||||
|
class Symbol
|
||||||
|
def to_json #:nodoc:
|
||||||
|
ActiveSupport::JSON.encode(to_s)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class TrueClass
|
||||||
|
def to_json #:nodoc:
|
||||||
|
'true'
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,45 @@
|
||||||
|
require 'active_support/json/variable'
|
||||||
|
|
||||||
|
require 'active_support/json/encoders/object' # Require this file explicitly for rdoc
|
||||||
|
Dir[File.dirname(__FILE__) + '/encoders/**/*.rb'].each { |file| require file[0..-4] }
|
||||||
|
|
||||||
|
module ActiveSupport
|
||||||
|
module JSON
|
||||||
|
# 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
|
||||||
|
|
||||||
|
class CircularReferenceError < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
REFERENCE_STACK_VARIABLE = :json_reference_stack #:nodoc:
|
||||||
|
|
||||||
|
# Converts a Ruby object into a JSON string.
|
||||||
|
def encode(value)
|
||||||
|
raise_on_circular_reference(value) do
|
||||||
|
value.send(:to_json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_unquote_identifier?(key) #:nodoc:
|
||||||
|
unquote_hash_key_identifiers &&
|
||||||
|
ActiveSupport::JSON.valid_identifier?(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
def raise_on_circular_reference(value) #:nodoc:
|
||||||
|
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
|
|
@ -0,0 +1,10 @@
|
||||||
|
module ActiveSupport
|
||||||
|
module JSON
|
||||||
|
# A string that returns itself as as its JSON-encoded form.
|
||||||
|
class Variable < String
|
||||||
|
def to_json
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -48,13 +48,13 @@ module Kernel #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Object #:nodoc:
|
class Object
|
||||||
class << self
|
class << self
|
||||||
alias_method :blank_slate_method_added, :method_added
|
alias_method :blank_slate_method_added, :method_added
|
||||||
|
|
||||||
# Detect method additions to Object and remove them in the
|
# Detect method additions to Object and remove them in the
|
||||||
# BlankSlate class.
|
# BlankSlate class.
|
||||||
def method_added(name)
|
def method_added(name) #:nodoc:
|
||||||
blank_slate_method_added(name)
|
blank_slate_method_added(name)
|
||||||
return if self != Object
|
return if self != Object
|
||||||
Builder::BlankSlate.hide(name)
|
Builder::BlankSlate.hide(name)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
require File.dirname(__FILE__) + '/../abstract_unit'
|
||||||
|
|
||||||
|
class TestJSONDecoding < Test::Unit::TestCase
|
||||||
|
TESTS = {
|
||||||
|
%({"returnTo":{"/categories":"/"}}) => {"returnTo" => {"/categories" => "/"}},
|
||||||
|
%({returnTo:{"/categories":"/"}}) => {"returnTo" => {"/categories" => "/"}},
|
||||||
|
%({"return\\"To\\":":{"/categories":"/"}}) => {"return\"To\":" => {"/categories" => "/"}},
|
||||||
|
%({"returnTo":{"/categories":1}}) => {"returnTo" => {"/categories" => 1}},
|
||||||
|
%({"returnTo":[1,"a"]}) => {"returnTo" => [1, "a"]},
|
||||||
|
%({"returnTo":[1,"\\"a\\",", "b"]}) => {"returnTo" => [1, "\"a\",", "b"]},
|
||||||
|
%([]) => [],
|
||||||
|
%({}) => {},
|
||||||
|
%(1) => 1,
|
||||||
|
%("") => "",
|
||||||
|
%("\\"") => "\"",
|
||||||
|
%(null) => nil,
|
||||||
|
%(true) => true,
|
||||||
|
%(false) => false
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_json_decoding
|
||||||
|
TESTS.each do |json, expected|
|
||||||
|
assert_nothing_raised do
|
||||||
|
assert_equal expected, ActiveSupport::JSON.decode(json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,12 +1,12 @@
|
||||||
require File.dirname(__FILE__) + '/abstract_unit'
|
require File.dirname(__FILE__) + '/../abstract_unit'
|
||||||
|
|
||||||
class Foo
|
class TestJSONEncoding < Test::Unit::TestCase
|
||||||
def initialize(a, b)
|
class Foo
|
||||||
@a, @b = a, b
|
def initialize(a, b)
|
||||||
|
@a, @b = a, b
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
class TestJSONEmitters < Test::Unit::TestCase
|
|
||||||
TrueTests = [[ true, %(true) ]]
|
TrueTests = [[ true, %(true) ]]
|
||||||
FalseTests = [[ false, %(false) ]]
|
FalseTests = [[ false, %(false) ]]
|
||||||
NilTests = [[ nil, %(null) ]]
|
NilTests = [[ nil, %(null) ]]
|
||||||
|
@ -70,9 +70,14 @@ class TestJSONEmitters < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unquote_hash_key_identifiers
|
def test_unquote_hash_key_identifiers
|
||||||
values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
|
values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
|
||||||
assert_equal %({"a": "a", 0: 0, "_": "_", 1: 1, "$": "$", "A": "A", "A0B": "A0B", "A0": "A0"}), values.to_json
|
assert_equal %w( "$" "A" "A0" "A0B" "_" "a" 0 1 ), object_keys(values.to_json)
|
||||||
unquote(true) { assert_equal %({a: "a", 0: 0, _: "_", 1: 1, $: "$", A: "A", A0B: "A0B", A0: "A0"}), values.to_json }
|
unquote(true) { assert_equal %w( $ 0 1 A A0 A0B _ a ), object_keys(values.to_json) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unquote_hash_key_identifiers_ignores_javascript_reserved_words
|
||||||
|
values = {"hello" => "world", "this" => "that", "with" => "foo"}
|
||||||
|
unquote(true) { assert_equal %w( "this" "with" hello ), object_keys(values.to_json) }
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
@ -84,4 +89,8 @@ class TestJSONEmitters < Test::Unit::TestCase
|
||||||
ActiveSupport::JSON.unquote_hash_key_identifiers = previous_value if block_given?
|
ActiveSupport::JSON.unquote_hash_key_identifiers = previous_value if block_given?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def object_keys(json_object)
|
||||||
|
json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
Loading…
Reference in New Issue