Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add ActiveSupport::JSON and Object#to_json for converting Ruby object…

…s to JSON strings

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3356 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit e567a5eb1afe1ac38f1da37f1c1e3922bbf79d2a 1 parent 0b55ce7
@sstephenson sstephenson authored
View
2  activesupport/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Add ActiveSupport::JSON and Object#to_json for converting Ruby objects to JSON strings. [Sam Stephenson]
+
* Add Object#with_options for DRYing up multiple calls to methods having shared options. [Sam Stephenson] Example:
ActionController::Routing::Routes.draw do |map|
View
4 activesupport/lib/active_support.rb
@@ -34,4 +34,6 @@
require 'active_support/ordered_options'
require 'active_support/option_merger'
-require 'active_support/values/time_zone'
+require 'active_support/values/time_zone'
+
+require 'active_support/json'
View
11 activesupport/lib/active_support/core_ext/object_and_class.rb
@@ -54,6 +54,17 @@ def suppress(*exception_classes)
def with_options(options)
yield ActiveSupport::OptionMerger.new(self, options)
end
+
+ def instance_values
+ instance_variables.inject({}) do |values, name|
+ values[name[1..-1]] = instance_variable_get(name)
+ values
+ end
+ end
+
+ def to_json
+ ActiveSupport::JSON.encode(self)
+ end
end
class Class #:nodoc:
View
2  activesupport/lib/active_support/core_ext/string.rb
@@ -2,10 +2,12 @@
require File.dirname(__FILE__) + '/string/conversions'
require File.dirname(__FILE__) + '/string/access'
require File.dirname(__FILE__) + '/string/starts_ends_with'
+require File.dirname(__FILE__) + '/string/iterators'
class String #:nodoc:
include ActiveSupport::CoreExtensions::String::Access
include ActiveSupport::CoreExtensions::String::Conversions
include ActiveSupport::CoreExtensions::String::Inflections
include ActiveSupport::CoreExtensions::String::StartsEndsWith
+ include ActiveSupport::CoreExtensions::String::Iterators
end
View
17 activesupport/lib/active_support/core_ext/string/iterators.rb
@@ -0,0 +1,17 @@
+require 'strscan'
+
+module ActiveSupport #:nodoc:
+ module CoreExtensions #:nodoc:
+ module String #:nodoc:
+ # Custom string iterators
+ module Iterators
+ # Yields a single-character string for each character in the string.
+ # When $KCODE = 'UTF8', multi-byte characters are yielded appropriately.
+ def each_char
+ scanner, char = StringScanner.new(self), /./mu
+ loop { yield(scanner.scan(char) || break) }
+ end
+ end
+ end
+ end
+end
View
28 activesupport/lib/active_support/json.rb
@@ -0,0 +1,28 @@
+require 'active_support/json/encoders'
+
+module ActiveSupport
+ module JSON #:nodoc:
+ class CircularReferenceError < StandardError; end
+
+ class << self
+ REFERENCE_STACK_VARIABLE = :json_reference_stack
+
+ def encode(value)
+ raise_on_circular_reference(value) do
+ Encoders[value.class].call(value)
+ 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
View
25 activesupport/lib/active_support/json/encoders.rb
@@ -0,0 +1,25 @@
+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
View
61 activesupport/lib/active_support/json/encoders/core.rb
@@ -0,0 +1,61 @@
+module ActiveSupport
+ module JSON #:nodoc:
+ module Encoders
+ 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
+
+ define_encoder String do |string|
+ returning value = '"' do
+ string.each_char do |char|
+ value << case
+ when char == "\010": '\b'
+ when char == "\f": '\f'
+ when char == "\n": '\n'
+ when char == "\r": '\r'
+ when char == "\t": '\t'
+ when char == '"': '\"'
+ when char == '\\': '\\\\'
+ when char.length > 1: "\\u#{'%04x' % char.unpack('U').first}"
+ else; char
+ end
+ end
+ value << '"'
+ end
+ 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 |pair|
+ pair.map { |value| value.to_json } * ': '
+ end * ', '
+ result << '}'
+ end
+ end
+ end
+ end
+end
View
7 activesupport/test/core_ext/object_and_class_ext_test.rb
@@ -104,4 +104,11 @@ def protected_instance_variables
assert !@dest.instance_variables.include?('@quux')
assert_equal 'baz', @dest.instance_variable_get('@baz')
end
+
+ def test_instance_values
+ object = Object.new
+ object.instance_variable_set :@a, 1
+ object.instance_variable_set :@b, 2
+ assert_equal({'a' => 1, 'b' => 2}, object.instance_values)
+ end
end
View
10 activesupport/test/core_ext/string_ext_test.rb
@@ -89,4 +89,14 @@ def test_starts_ends_with
assert s.ends_with?('lo')
assert !s.ends_with?('el')
end
+
+ def test_each_char_with_utf8_string_when_kcode_is_utf8
+ old_kcode, $KCODE = $KCODE, 'UTF8'
+ '€2.99'.each_char do |char|
+ assert_not_equal 1, char.length
+ break
+ end
+ ensure
+ $KCODE = old_kcode
+ end
end
View
53 activesupport/test/json.rb
@@ -0,0 +1,53 @@
+$:.unshift File.dirname(__FILE__) + '/../lib'
+require 'active_support'
+require 'test/unit'
+
+class Foo
+ def initialize(a, b)
+ @a, @b = a, b
+ end
+end
+
+class TestJSONEmitters < Test::Unit::TestCase
+ TrueTests = [[ true, %(true) ]]
+ FalseTests = [[ false, %(false) ]]
+ NilTests = [[ nil, %(null) ]]
+ NumericTests = [[ 1, %(1) ],
+ [ 2.5, %(2.5) ]]
+
+ StringTests = [[ 'this is the string', %("this is the string") ],
+ [ 'a "string" with quotes', %("a \\"string\\" with quotes") ]]
+
+ ArrayTests = [[ ['a', 'b', 'c'], %([\"a\", \"b\", \"c\"]) ],
+ [ [1, 'a', :b, nil, false], %([1, \"a\", \"b\", null, false]) ]]
+
+ HashTests = [[ {:a => :b, :c => :d}, %({\"c\": \"d\", \"a\": \"b\"}) ]]
+
+ SymbolTests = [[ :a, %("a") ],
+ [ :this, %("this") ],
+ [ :"a b", %("a b") ]]
+
+ ObjectTests = [[ Foo.new(1, 2), %({\"a\": 1, \"b\": 2}) ]]
+
+ constants.grep(/Tests$/).each do |class_tests|
+ define_method("test_#{class_tests[0..-6].downcase}") do
+ self.class.const_get(class_tests).each do |pair|
+ assert_equal pair.last, pair.first.to_json
+ end
+ end
+ end
+
+ def test_utf8_string_encoded_properly_when_kcode_is_utf8
+ old_kcode, $KCODE = $KCODE, 'UTF8'
+ assert_equal '"\\u20ac2.99"', '€2.99'.to_json
+ assert_equal '"\\u270e\\u263a"', '✎☺'.to_json
+ ensure
+ $KCODE = old_kcode
+ end
+
+ def test_exception_raised_when_encoding_circular_reference
+ a = [1]
+ a << a
+ assert_raises(ActiveSupport::JSON::CircularReferenceError) { a.to_json }
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.