aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Kemper <jeremy@bitsweat.net>2010-04-26 19:32:23 -0700
committerJeremy Kemper <jeremy@bitsweat.net>2010-04-26 19:55:39 -0700
commitc1d73270717f30498f8f4d55d6695509107c2834 (patch)
tree88529812e5afe3cb15e60da2bc3fa67d3f4b09e4
parent43e2fd93b4fa92ca23d8bc8e68e1bf5a94038461 (diff)
downloadrails-c1d73270717f30498f8f4d55d6695509107c2834.tar.gz
rails-c1d73270717f30498f8f4d55d6695509107c2834.tar.bz2
rails-c1d73270717f30498f8f4d55d6695509107c2834.zip
JSON: encode objects that don't have a native JSON representation using to_hash, if available, instead of instance_values (the old fallback) or to_s (other encoders' default). Encode BigDecimal and Regexp encode as strings to conform with other encoders. Try to transcode non-UTF-8 strings.
-rw-r--r--activesupport/CHANGELOG5
-rw-r--r--activesupport/lib/active_support/json/encoding.rb24
-rw-r--r--activesupport/test/json/encoding_test.rb21
3 files changed, 43 insertions, 7 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 5ada5a1e9b..c47839b001 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,3 +1,8 @@
+*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+
+* JSON: encode objects that don't have a native JSON representation using to_hash, if available, instead of instance_values (the old fallback) or to_s (other encoders' default). Encode BigDecimal and Regexp encode as strings to conform with other encoders. Try to transcode non-UTF-8 strings. [Jeremy Kemper]
+
+
*Rails 3.0.0 [beta 3] (April 13th, 2010)*
* HashWithIndifferentAccess: remove inherited symbolize_keys! since its keys are always strings. [Santiago Pastorino]
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 8ba45f7ea2..0f38fd0e89 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,4 +1,5 @@
# encoding: utf-8
+require 'bigdecimal'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
@@ -102,7 +103,9 @@ module ActiveSupport
end
def escape(string)
- string = string.dup.force_encoding(::Encoding::BINARY) if string.respond_to?(:force_encoding)
+ if string.respond_to?(:force_encoding)
+ string = string.encode(::Encoding::UTF_8, undef: :replace).force_encoding(::Encoding::BINARY)
+ end
json = string.
gsub(escape_regex) { |s| ESCAPED_CHARS[s] }.
gsub(/([\xC0-\xDF][\x80-\xBF]|
@@ -110,7 +113,9 @@ module ActiveSupport
[\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s|
s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&')
}
- %("#{json}")
+ json = %("#{json}")
+ json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding)
+ json
end
end
@@ -128,7 +133,13 @@ class Object
ActiveSupport::JSON.encode(self, options)
end
- def as_json(options = nil) instance_values end #:nodoc:
+ def as_json(options = nil) #:nodoc:
+ if respond_to?(:to_hash)
+ to_hash
+ else
+ instance_values
+ end
+ end
end
# A string that returns itself as its JSON-encoded form.
@@ -166,9 +177,12 @@ class Numeric
def encode_json(encoder) to_s end #:nodoc:
end
+class BigDecimal
+ def as_json(options = nil) to_s end #:nodoc:
+end
+
class Regexp
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) inspect end #:nodoc:
+ def as_json(options = nil) to_s end #:nodoc:
end
module Enumerable
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 188b799f3f..ff95c0ca18 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -9,6 +9,12 @@ class TestJSONEncoding < Test::Unit::TestCase
end
end
+ class Hashlike
+ def to_hash
+ { :a => 1 }
+ end
+ end
+
class Custom
def as_json(options)
'custom'
@@ -19,7 +25,8 @@ class TestJSONEncoding < Test::Unit::TestCase
FalseTests = [[ false, %(false) ]]
NilTests = [[ nil, %(null) ]]
NumericTests = [[ 1, %(1) ],
- [ 2.5, %(2.5) ]]
+ [ 2.5, %(2.5) ],
+ [ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]]
StringTests = [[ 'this is the <string>', %("this is the \\u003Cstring\\u003E")],
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
@@ -35,11 +42,12 @@ class TestJSONEncoding < Test::Unit::TestCase
[ :"a b", %("a b") ]]
ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
+ HashlikeTests = [[ Hashlike.new, %({\"a\":1}) ]]
CustomTests = [[ Custom.new, '"custom"' ]]
VariableTests = [[ ActiveSupport::JSON::Variable.new('foo'), 'foo'],
[ ActiveSupport::JSON::Variable.new('alert("foo")'), 'alert("foo")']]
- RegexpTests = [[ /^a/, '/^a/' ], [/^\w{1,2}[a-z]+/ix, '/^\\w{1,2}[a-z]+/ix']]
+ RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
@@ -91,6 +99,15 @@ class TestJSONEncoding < Test::Unit::TestCase
end
end
+ if '1.9'.respond_to?(:force_encoding)
+ def test_non_utf8_string_transcodes
+ s = '二'.encode('Shift_JIS')
+ result = ActiveSupport::JSON.encode(s)
+ assert_equal '"\\u4e8c"', result
+ assert_equal Encoding::UTF_8, result.encoding
+ end
+ end
+
def test_exception_raised_when_encoding_circular_reference
a = [1]
a << a