Skip to content

Commit 613931f

Browse files
committed
Allow opting out of applying key format to values
Since rails#486, key format is also applied to nested hashes that are passed as values: json.key_format! camelize: :lower json.settings({some_value: "abc"}) # => { "settings": { "someValue": "abc" }} This breaks code that relied on the previous behavior. Add a `deep_format_keys!` directive that can be used to opt out of the new behavior.
1 parent b480e61 commit 613931f

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,33 @@ environment.rb for example):
274274
Jbuilder.key_format camelize: :lower
275275
```
276276

277+
Since version 2.11, key format is also applied to nested hashes that
278+
are passed as values:
279+
280+
``` ruby
281+
json.key_format! camelize: :lower
282+
json.settings({some_value: "abc"})
283+
284+
# => { "settings": { "someValue": "abc" }}
285+
```
286+
287+
If you'd like to preserve the previous behavior, you can opt out:
288+
289+
``` ruby
290+
json.key_format! camelize: :lower
291+
json.deep_format_keys! false
292+
json.settings({some_value: "abc"})
293+
294+
# => { "settings": { "some_value": "abc" }}
295+
```
296+
297+
You can set this globally with the class method `deep_format_keys` (from inside your
298+
environment.rb for example):
299+
300+
``` ruby
301+
Jbuilder.deep_format_keys false
302+
```
303+
277304
## Contributing to Jbuilder
278305

279306
Jbuilder is the work of many contributors. You're encouraged to submit pull requests, propose

lib/jbuilder.rb

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
class Jbuilder
1010
@@key_formatter = nil
1111
@@ignore_nil = false
12+
@@deep_format_keys = true
1213

1314
def initialize(options = {})
1415
@attributes = {}
1516

1617
@key_formatter = options.fetch(:key_formatter){ @@key_formatter ? @@key_formatter.clone : nil}
1718
@ignore_nil = options.fetch(:ignore_nil, @@ignore_nil)
19+
@deep_format_keys = options.fetch(:deep_format_keys, @@deep_format_keys)
1820

1921
yield self if ::Kernel.block_given?
2022
end
@@ -131,6 +133,32 @@ def self.ignore_nil(value = true)
131133
@@ignore_nil = value
132134
end
133135

136+
# Since version 2.11 key format is also applied to nested hashes and
137+
# arrays passed to methods like set!, merge! or array!. You can opt
138+
# out of this behavior.
139+
#
140+
# Example:
141+
#
142+
# json.key_format! camelize: :lower
143+
# json.settings({some_value: "abc"})
144+
#
145+
# { "settings": { "someValue": "abc" }}
146+
#
147+
# json.key_format! camelize: :lower
148+
# json.deep_format_keys! false
149+
# json.settings({some_value: "abc"})
150+
#
151+
# { "settings": { "some_value": "abc" }}
152+
#
153+
def deep_format_keys!(value)
154+
@deep_format_keys = value
155+
end
156+
157+
# Same as instance method deep_format_keys! except sets the default.
158+
def self.deep_format_keys(value)
159+
@@deep_format_keys = value
160+
end
161+
134162
# Turns the current element into an array and yields a builder to add a hash.
135163
#
136164
# Example:
@@ -288,6 +316,8 @@ def _key(key)
288316
end
289317

290318
def _format_keys(hash_or_array)
319+
return hash_or_array unless @deep_format_keys
320+
291321
if ::Array === hash_or_array
292322
hash_or_array.map { |value| _format_keys(value) }
293323
elsif ::Hash === hash_or_array
@@ -312,12 +342,12 @@ def _map_collection(collection)
312342
end
313343

314344
def _scope
315-
parent_attributes, parent_formatter = @attributes, @key_formatter
345+
parent_attributes, parent_formatter, parent_deep_format_keys = @attributes, @key_formatter, @deep_format_keys
316346
@attributes = BLANK
317347
yield
318348
@attributes
319349
ensure
320-
@attributes, @key_formatter = parent_attributes, parent_formatter
350+
@attributes, @key_formatter, @deep_format_keys = parent_attributes, parent_formatter, parent_deep_format_keys
321351
end
322352

323353
def _is_collection?(object)

test/jbuilder_test.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,45 @@ class JbuilderTest < ActiveSupport::TestCase
729729
assert_empty cache
730730
end
731731

732+
test 'applying key_format! deeply can be disabled' do
733+
names = { first_name: 'camel', last_name: 'case' }
734+
result = jbuild do |json|
735+
json.key_format! camelize: :lower
736+
json.deep_format_keys! false
737+
json.set! :all_names, names
738+
end
739+
740+
assert_equal %i[first_name last_name], result['allNames'].keys
741+
end
742+
743+
test 'applying key_format! deeply can be disabled per scope' do
744+
names = { first_name: 'camel', last_name: 'case' }
745+
result = jbuild do |json|
746+
json.key_format! camelize: :lower
747+
json.scope do
748+
json.deep_format_keys! false
749+
json.set! :all_names, names
750+
end
751+
json.set! :all_names, names
752+
end
753+
754+
assert_equal %i[first_name last_name], result['scope']['allNames'].keys
755+
assert_equal %w[firstName lastName], result['allNames'].keys
756+
end
757+
758+
test 'applying key_format! deeply can be disabled globally' do
759+
names = { first_name: 'camel', last_name: 'case' }
760+
761+
Jbuilder.deep_format_keys false
762+
result = jbuild do |json|
763+
json.key_format! camelize: :lower
764+
json.set! :all_names, names
765+
end
766+
767+
assert_equal %i[first_name last_name], result['allNames'].keys
768+
Jbuilder.send(:class_variable_set, '@@deep_format_keys', true)
769+
end
770+
732771
test 'ignore_nil! without a parameter' do
733772
result = jbuild do |json|
734773
json.ignore_nil!

0 commit comments

Comments
 (0)