diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 33cce48323..f6b09c84d9 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Don't quote hash keys in Hash#to_json if they're valid JavaScript identifiers. Disable this with ActiveSupport::JSON.unquote_hash_key_identifiers = false if you need strict JSON compliance. [Sam Stephenson] + * Lazily load the Unicode Database in the UTF-8 Handler [Rick Olson] * Update dependencies to delete partially loaded constants. [Nicholas Seckar] diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb index 7d5304ddd4..d1203bd21b 100644 --- a/activesupport/lib/active_support/json.rb +++ b/activesupport/lib/active_support/json.rb @@ -4,14 +4,20 @@ module ActiveSupport module JSON #:nodoc: class CircularReferenceError < StandardError #:nodoc: end - # returns the literal string as its JSON encoded form. Useful for passing javascript variables into functions. - # - # page.call 'Element.show', ActiveSupport::JSON::Variable.new("$$(#items li)") + + # A string that returns itself as as its JSON-encoded form. class Variable < String #:nodoc: def to_json self end end + + # 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 << self REFERENCE_STACK_VARIABLE = :json_reference_stack @@ -22,6 +28,11 @@ def encode(value) end end + def can_unquote_identifier?(key) + return false unless unquote_hash_key_identifiers + key.to_s =~ /^[[:alpha:]_$][[:alnum:]_$]*$/ + end + protected def raise_on_circular_reference(value) stack = Thread.current[REFERENCE_STACK_VARIABLE] ||= [] diff --git a/activesupport/lib/active_support/json/encoders/core.rb b/activesupport/lib/active_support/json/encoders/core.rb index d8571be9c8..f6cdfbcde5 100644 --- a/activesupport/lib/active_support/json/encoders/core.rb +++ b/activesupport/lib/active_support/json/encoders/core.rb @@ -51,8 +51,10 @@ module Encoders #:nodoc: define_encoder Hash do |hash| returning result = '{' do - result << hash.map do |pair| - pair.map { |value| value.to_json } * ': ' + 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 diff --git a/activesupport/test/json.rb b/activesupport/test/json.rb index 33c5a64ba5..0274dd073a 100644 --- a/activesupport/test/json.rb +++ b/activesupport/test/json.rb @@ -36,6 +36,14 @@ class TestJSONEmitters < Test::Unit::TestCase end end end + + def setup + unquote(false) + end + + def teardown + unquote(true) + end def test_hash_encoding assert_equal %({\"a\": \"b\"}), { :a => :b }.to_json @@ -60,4 +68,20 @@ def test_exception_raised_when_encoding_circular_reference a << a assert_raises(ActiveSupport::JSON::CircularReferenceError) { a.to_json } end + + def test_unquote_hash_key_identifiers + 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 + unquote(true) { assert_equal %({a: "a", 0: 0, _: "_", 1: 1, $: "$", A: "A", A0B: "A0B", A0: "A0"}), values.to_json } + end + + protected + def unquote(value) + previous_value = ActiveSupport::JSON.unquote_hash_key_identifiers + ActiveSupport::JSON.unquote_hash_key_identifiers = value + yield if block_given? + ensure + ActiveSupport::JSON.unquote_hash_key_identifiers = previous_value if block_given? + end + end