diff options
author | Sam Stephenson <sam@37signals.com> | 2005-12-27 03:11:03 +0000 |
---|---|---|
committer | Sam Stephenson <sam@37signals.com> | 2005-12-27 03:11:03 +0000 |
commit | e567a5eb1afe1ac38f1da37f1c1e3922bbf79d2a (patch) | |
tree | 47baca37670f0ad23d803c2f939199147efe4287 /activesupport | |
parent | 0b55ce7191b167d67ec7ccc7eb8db2e5fac4f2e1 (diff) | |
download | rails-e567a5eb1afe1ac38f1da37f1c1e3922bbf79d2a.tar.gz rails-e567a5eb1afe1ac38f1da37f1c1e3922bbf79d2a.tar.bz2 rails-e567a5eb1afe1ac38f1da37f1c1e3922bbf79d2a.zip |
Add ActiveSupport::JSON and Object#to_json for converting Ruby objects to JSON strings
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3356 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activesupport')
-rw-r--r-- | activesupport/CHANGELOG | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support.rb | 4 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/object_and_class.rb | 11 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/string.rb | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/string/iterators.rb | 17 | ||||
-rw-r--r-- | activesupport/lib/active_support/json.rb | 28 | ||||
-rw-r--r-- | activesupport/lib/active_support/json/encoders.rb | 25 | ||||
-rw-r--r-- | activesupport/lib/active_support/json/encoders/core.rb | 61 | ||||
-rw-r--r-- | activesupport/test/core_ext/object_and_class_ext_test.rb | 7 | ||||
-rw-r--r-- | activesupport/test/core_ext/string_ext_test.rb | 10 | ||||
-rw-r--r-- | activesupport/test/json.rb | 53 |
11 files changed, 219 insertions, 1 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 0f2d0abfd1..397dc3d1ef 100644 --- a/activesupport/CHANGELOG +++ b/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| diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 1cace11e9b..f498a8e1f5 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -34,4 +34,6 @@ require 'active_support/dependencies' require 'active_support/ordered_options' require 'active_support/option_merger' -require 'active_support/values/time_zone'
\ No newline at end of file +require 'active_support/values/time_zone' + +require 'active_support/json' diff --git a/activesupport/lib/active_support/core_ext/object_and_class.rb b/activesupport/lib/active_support/core_ext/object_and_class.rb index aef1f22c6f..4856a9f5d5 100644 --- a/activesupport/lib/active_support/core_ext/object_and_class.rb +++ b/activesupport/lib/active_support/core_ext/object_and_class.rb @@ -54,6 +54,17 @@ class Object #:nodoc: 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: diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index 979f8a0515..240e1ff1da 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -2,10 +2,12 @@ require File.dirname(__FILE__) + '/string/inflections' 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 diff --git a/activesupport/lib/active_support/core_ext/string/iterators.rb b/activesupport/lib/active_support/core_ext/string/iterators.rb new file mode 100644 index 0000000000..73114d9d5f --- /dev/null +++ b/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 diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb new file mode 100644 index 0000000000..77c7225c66 --- /dev/null +++ b/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 diff --git a/activesupport/lib/active_support/json/encoders.rb b/activesupport/lib/active_support/json/encoders.rb new file mode 100644 index 0000000000..c3e3619f59 --- /dev/null +++ b/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 diff --git a/activesupport/lib/active_support/json/encoders/core.rb b/activesupport/lib/active_support/json/encoders/core.rb new file mode 100644 index 0000000000..003c938be4 --- /dev/null +++ b/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 diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index 25420b7f6f..22e4c6b8b4 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -104,4 +104,11 @@ class ObjectInstanceVariableTest < Test::Unit::TestCase 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 diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 0e82badad7..92f57beb5f 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -89,4 +89,14 @@ class StringInflectionsTest < Test::Unit::TestCase 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 diff --git a/activesupport/test/json.rb b/activesupport/test/json.rb new file mode 100644 index 0000000000..c5b597abd7 --- /dev/null +++ b/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 |