Skip to content

Commit ade1b30

Browse files
committed
Disable deeply formatting keys of nested hashes by default
Since #486, key format was 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 into this new behavior: json.key_format! camelize: :lower json.deep_format_keys! json.settings({some_value: "abc"}) # => { "settings": { "someValue": "abc" }}
1 parent b480e61 commit ade1b30

File tree

3 files changed

+122
-11
lines changed

3 files changed

+122
-11
lines changed

README.md

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

277+
By default, key format is not applied to keys of hashes that are
278+
passed to methods like `set!`, `array!` or `merge!`. You can opt into
279+
deeply transforming these as well:
280+
281+
``` ruby
282+
json.key_format! camelize: :lower
283+
json.deep_format_keys!
284+
json.settings([{some_value: "abc"}])
285+
286+
# => { "settings": [{ "someValue": "abc" }]}
287+
```
288+
289+
You can set this globally with the class method `deep_format_keys` (from inside your
290+
environment.rb for example):
291+
292+
``` ruby
293+
Jbuilder.deep_format_keys true
294+
```
295+
277296
## Contributing to Jbuilder
278297

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

lib/jbuilder.rb

Lines changed: 31 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 = false
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,31 @@ def self.ignore_nil(value = true)
131133
@@ignore_nil = value
132134
end
133135

136+
# Deeply apply key format to nested hashes and arrays passed to
137+
# methods like set!, merge! or array!.
138+
#
139+
# Example:
140+
#
141+
# json.key_format! camelize: :lower
142+
# json.settings({some_value: "abc"})
143+
#
144+
# { "settings": { "some_value": "abc" }}
145+
#
146+
# json.key_format! camelize: :lower
147+
# json.deep_format_keys!
148+
# json.settings({some_value: "abc"})
149+
#
150+
# { "settings": { "someValue": "abc" }}
151+
#
152+
def deep_format_keys!(value = true)
153+
@deep_format_keys = value
154+
end
155+
156+
# Same as instance method deep_format_keys! except sets the default.
157+
def self.deep_format_keys(value = true)
158+
@@deep_format_keys = value
159+
end
160+
134161
# Turns the current element into an array and yields a builder to add a hash.
135162
#
136163
# Example:
@@ -288,6 +315,8 @@ def _key(key)
288315
end
289316

290317
def _format_keys(hash_or_array)
318+
return hash_or_array unless @deep_format_keys
319+
291320
if ::Array === hash_or_array
292321
hash_or_array.map { |value| _format_keys(value) }
293322
elsif ::Hash === hash_or_array
@@ -312,12 +341,12 @@ def _map_collection(collection)
312341
end
313342

314343
def _scope
315-
parent_attributes, parent_formatter = @attributes, @key_formatter
344+
parent_attributes, parent_formatter, parent_deep_format_keys = @attributes, @key_formatter, @deep_format_keys
316345
@attributes = BLANK
317346
yield
318347
@attributes
319348
ensure
320-
@attributes, @key_formatter = parent_attributes, parent_formatter
349+
@attributes, @key_formatter, @deep_format_keys = parent_attributes, parent_formatter, parent_deep_format_keys
321350
end
322351

323352
def _is_collection?(object)

test/jbuilder_test.rb

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -623,92 +623,155 @@ class JbuilderTest < ActiveSupport::TestCase
623623
assert_equal ['oats and friends'], result.keys
624624
end
625625

626-
test 'key_format! with merge!' do
626+
test 'key_format! is not applied deeply by default' do
627+
names = { first_name: 'camel', last_name: 'case' }
628+
result = jbuild do |json|
629+
json.key_format! camelize: :lower
630+
json.set! :all_names, names
631+
end
632+
633+
assert_equal %i[first_name last_name], result['allNames'].keys
634+
end
635+
636+
test 'applying key_format! deeply can be enabled per scope' do
637+
names = { first_name: 'camel', last_name: 'case' }
638+
result = jbuild do |json|
639+
json.key_format! camelize: :lower
640+
json.scope do
641+
json.deep_format_keys!
642+
json.set! :all_names, names
643+
end
644+
json.set! :all_names, names
645+
end
646+
647+
assert_equal %w[firstName lastName], result['scope']['allNames'].keys
648+
assert_equal %i[first_name last_name], result['allNames'].keys
649+
end
650+
651+
test 'applying key_format! deeply can be disabled per scope' do
652+
names = { first_name: 'camel', last_name: 'case' }
653+
result = jbuild do |json|
654+
json.key_format! camelize: :lower
655+
json.deep_format_keys!
656+
json.set! :all_names, names
657+
json.scope do
658+
json.deep_format_keys! false
659+
json.set! :all_names, names
660+
end
661+
end
662+
663+
assert_equal %w[firstName lastName], result['allNames'].keys
664+
assert_equal %i[first_name last_name], result['scope']['allNames'].keys
665+
end
666+
667+
test 'applying key_format! deeply can be enabled globally' do
668+
names = { first_name: 'camel', last_name: 'case' }
669+
670+
Jbuilder.deep_format_keys true
671+
result = jbuild do |json|
672+
json.key_format! camelize: :lower
673+
json.set! :all_names, names
674+
end
675+
676+
assert_equal %w[firstName lastName], result['allNames'].keys
677+
Jbuilder.send(:class_variable_set, '@@deep_format_keys', false)
678+
end
679+
680+
test 'deep key_format! with merge!' do
627681
hash = { camel_style: 'for JS' }
628682
result = jbuild do |json|
629683
json.key_format! camelize: :lower
684+
json.deep_format_keys!
630685
json.merge! hash
631686
end
632687

633688
assert_equal ['camelStyle'], result.keys
634689
end
635690

636-
test 'key_format! with merge! deep' do
691+
test 'deep key_format! with merge! deep' do
637692
hash = { camel_style: { sub_attr: 'for JS' } }
638693
result = jbuild do |json|
639694
json.key_format! camelize: :lower
695+
json.deep_format_keys!
640696
json.merge! hash
641697
end
642698

643699
assert_equal ['subAttr'], result['camelStyle'].keys
644700
end
645701

646-
test 'key_format! with set! array of hashes' do
702+
test 'deep key_format! with set! array of hashes' do
647703
names = [{ first_name: 'camel', last_name: 'case' }]
648704
result = jbuild do |json|
649705
json.key_format! camelize: :lower
706+
json.deep_format_keys!
650707
json.set! :names, names
651708
end
652709

653710
assert_equal %w[firstName lastName], result['names'][0].keys
654711
end
655712

656-
test 'key_format! with set! extracting hash from object' do
713+
test 'deep key_format! with set! extracting hash from object' do
657714
comment = Struct.new(:author).new({ first_name: 'camel', last_name: 'case' })
658715
result = jbuild do |json|
659716
json.key_format! camelize: :lower
717+
json.deep_format_keys!
660718
json.set! :comment, comment, :author
661719
end
662720

663721
assert_equal %w[firstName lastName], result['comment']['author'].keys
664722
end
665723

666-
test 'key_format! with array! of hashes' do
724+
test 'deep key_format! with array! of hashes' do
667725
names = [{ first_name: 'camel', last_name: 'case' }]
668726
result = jbuild do |json|
669727
json.key_format! camelize: :lower
728+
json.deep_format_keys!
670729
json.array! names
671730
end
672731

673732
assert_equal %w[firstName lastName], result[0].keys
674733
end
675734

676-
test 'key_format! with merge! array of hashes' do
735+
test 'deep key_format! with merge! array of hashes' do
677736
names = [{ first_name: 'camel', last_name: 'case' }]
678737
new_names = [{ first_name: 'snake', last_name: 'case' }]
679738
result = jbuild do |json|
680739
json.key_format! camelize: :lower
740+
json.deep_format_keys!
681741
json.array! names
682742
json.merge! new_names
683743
end
684744

685745
assert_equal %w[firstName lastName], result[1].keys
686746
end
687747

688-
test 'key_format! is applied to hash extracted from object' do
748+
test 'deep key_format! is applied to hash extracted from object' do
689749
comment = Struct.new(:author).new({ first_name: 'camel', last_name: 'case' })
690750
result = jbuild do |json|
691751
json.key_format! camelize: :lower
752+
json.deep_format_keys!
692753
json.extract! comment, :author
693754
end
694755

695756
assert_equal %w[firstName lastName], result['author'].keys
696757
end
697758

698-
test 'key_format! is applied to hash extracted from hash' do
759+
test 'deep key_format! is applied to hash extracted from hash' do
699760
comment = {author: { first_name: 'camel', last_name: 'case' }}
700761
result = jbuild do |json|
701762
json.key_format! camelize: :lower
763+
json.deep_format_keys!
702764
json.extract! comment, :author
703765
end
704766

705767
assert_equal %w[firstName lastName], result['author'].keys
706768
end
707769

708-
test 'key_format! is applied to hash extracted directly from array' do
770+
test 'deep key_format! is applied to hash extracted directly from array' do
709771
comments = [Struct.new(:author).new({ first_name: 'camel', last_name: 'case' })]
710772
result = jbuild do |json|
711773
json.key_format! camelize: :lower
774+
json.deep_format_keys!
712775
json.array! comments, :author
713776
end
714777

0 commit comments

Comments
 (0)