From c1d73270717f30498f8f4d55d6695509107c2834 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 26 Apr 2010 19:32:23 -0700 Subject: 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. --- activesupport/CHANGELOG | 5 +++++ activesupport/lib/active_support/json/encoding.rb | 24 ++++++++++++++++++----- activesupport/test/json/encoding_test.rb | 21 ++++++++++++++++++-- 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 ', %("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 -- cgit v1.2.3