Skip to content

Commit b480e61

Browse files
committed
Fix changing key format in child elements
Since rails#486, key format is applied deeply to hashes and arrays that are passed to `set!`, `merge!` and `array!`: json.key_format! :upcase json.set!(:foo, {some: "value"}) # => {"FOO": {"SOME": "value"}} json.key_format! :upcase json.merge!({some: "value"}) # => {"SOME": "value"} json.key_format! :upcase json.array!([{some: "value"}]) # => [{"SOME": "value"}] This also works for arrays and hashes extracted from objects: comment = Struct.new(:author).new({ first_name: 'John', last_name: 'Doe' }) json.key_format! camlize: :lower json.set!(:comment, comment, :author) # => {"comment": {"author": {"firstName": "John", "lastName": "Doe"}}} As a side effect of the change, key format is also applied to the result of nested blocks, making it impossible to change key format in the scope of a block: json.key_format! camelize: :lower json.level_one do json.key_format! :upcase json.value 'two' end # => jbuilder 2.10.0: {"levelOne": {"VALUE": "two"}} # => jbuilder 2.11.0: {"levelOne": {"vALUE": "two"}} The string "vALUE" results from calling `"value".upcase.camelize(:lower)`. The same happens when trying to change the key format inside of an `array!` block. This happens since key transformation was added in the `_merge_values` method, which is used both by `merge!` but also when `set!` is called with a block. To restore the previous behavior, we pull the `_transform_keys` call up into `merge!` itself. To make sure extracted hashes and arrays keep being transformed (see comment/author example above), we apply `_transform_keys` in `_extract_hash_values` and `_extract_method_values`. This also aligns the behavior of `extract!` which was left out in rails#486: comment = {author: { first_name: 'John', last_name: 'Doe' }} result = jbuild do |json| json.key_format! camelize: :lower json.extract! comment, :author end # => jbuilder 2.10 and 2.11: {"author": { :first_name => "John", :last_name => "Doe" }} # => now: {"author": { "firstName": "John", "lastName": "Doe" }} Finally, to fix `array!`, we make it call `_merge_values` directly instead of relying on `merge!`. `array!` then has to transform keys itself when a collection is passed to preserve the new behavior introduced by rails#486.
1 parent 18c06fe commit b480e61

File tree

2 files changed

+78
-8
lines changed

2 files changed

+78
-8
lines changed

lib/jbuilder.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,10 @@ def array!(collection = [], *attributes, &block)
190190
elsif attributes.any?
191191
_map_collection(collection) { |element| extract! element, *attributes }
192192
else
193-
collection.to_a
193+
_format_keys(collection.to_a)
194194
end
195195

196-
merge! array
196+
@attributes = _merge_values(@attributes, array)
197197
end
198198

199199
# Extracts the mentioned attributes or hash elements from the passed object and turns them into attributes of the JSON.
@@ -244,7 +244,7 @@ def attributes!
244244
# Merges hash, array, or Jbuilder instance into current builder.
245245
def merge!(object)
246246
hash_or_array = ::Jbuilder === object ? object.attributes! : object
247-
@attributes = _merge_values(@attributes, hash_or_array)
247+
@attributes = _merge_values(@attributes, _format_keys(hash_or_array))
248248
end
249249

250250
# Encodes the current builder as JSON.
@@ -255,11 +255,11 @@ def target!
255255
private
256256

257257
def _extract_hash_values(object, attributes)
258-
attributes.each{ |key| _set_value key, object.fetch(key) }
258+
attributes.each{ |key| _set_value key, _format_keys(object.fetch(key)) }
259259
end
260260

261261
def _extract_method_values(object, attributes)
262-
attributes.each{ |key| _set_value key, object.public_send(key) }
262+
attributes.each{ |key| _set_value key, _format_keys(object.public_send(key)) }
263263
end
264264

265265
def _merge_block(key)
@@ -273,11 +273,11 @@ def _merge_values(current_value, updates)
273273
if _blank?(updates)
274274
current_value
275275
elsif _blank?(current_value) || updates.nil? || current_value.empty? && ::Array === updates
276-
_format_keys(updates)
276+
updates
277277
elsif ::Array === current_value && ::Array === updates
278-
current_value + _format_keys(updates)
278+
current_value + updates
279279
elsif ::Hash === current_value && ::Hash === updates
280-
current_value.deep_merge(_format_keys(updates))
280+
current_value.deep_merge(updates)
281281
else
282282
raise MergeError.build(current_value, updates)
283283
end

test/jbuilder_test.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,36 @@ class JbuilderTest < ActiveSupport::TestCase
566566
assert_equal 'one', result['level1']
567567
end
568568

569+
test 'key_format! can be changed in child elements' do
570+
result = jbuild do |json|
571+
json.key_format! camelize: :lower
572+
573+
json.level_one do
574+
json.key_format! :upcase
575+
json.value 'two'
576+
end
577+
end
578+
579+
assert_equal ['levelOne'], result.keys
580+
assert_equal ['VALUE'], result['levelOne'].keys
581+
end
582+
583+
test 'key_format! can be changed in array!' do
584+
result = jbuild do |json|
585+
json.key_format! camelize: :lower
586+
587+
json.level_one do
588+
json.array! [{value: 'two'}] do |object|
589+
json.key_format! :upcase
590+
json.value object[:value]
591+
end
592+
end
593+
end
594+
595+
assert_equal ['levelOne'], result.keys
596+
assert_equal ['VALUE'], result['levelOne'][0].keys
597+
end
598+
569599
test 'key_format! with no parameter' do
570600
result = jbuild do |json|
571601
json.key_format! :upcase
@@ -623,6 +653,16 @@ class JbuilderTest < ActiveSupport::TestCase
623653
assert_equal %w[firstName lastName], result['names'][0].keys
624654
end
625655

656+
test 'key_format! with set! extracting hash from object' do
657+
comment = Struct.new(:author).new({ first_name: 'camel', last_name: 'case' })
658+
result = jbuild do |json|
659+
json.key_format! camelize: :lower
660+
json.set! :comment, comment, :author
661+
end
662+
663+
assert_equal %w[firstName lastName], result['comment']['author'].keys
664+
end
665+
626666
test 'key_format! with array! of hashes' do
627667
names = [{ first_name: 'camel', last_name: 'case' }]
628668
result = jbuild do |json|
@@ -645,6 +685,36 @@ class JbuilderTest < ActiveSupport::TestCase
645685
assert_equal %w[firstName lastName], result[1].keys
646686
end
647687

688+
test 'key_format! is applied to hash extracted from object' do
689+
comment = Struct.new(:author).new({ first_name: 'camel', last_name: 'case' })
690+
result = jbuild do |json|
691+
json.key_format! camelize: :lower
692+
json.extract! comment, :author
693+
end
694+
695+
assert_equal %w[firstName lastName], result['author'].keys
696+
end
697+
698+
test 'key_format! is applied to hash extracted from hash' do
699+
comment = {author: { first_name: 'camel', last_name: 'case' }}
700+
result = jbuild do |json|
701+
json.key_format! camelize: :lower
702+
json.extract! comment, :author
703+
end
704+
705+
assert_equal %w[firstName lastName], result['author'].keys
706+
end
707+
708+
test 'key_format! is applied to hash extracted directly from array' do
709+
comments = [Struct.new(:author).new({ first_name: 'camel', last_name: 'case' })]
710+
result = jbuild do |json|
711+
json.key_format! camelize: :lower
712+
json.array! comments, :author
713+
end
714+
715+
assert_equal %w[firstName lastName], result[0]['author'].keys
716+
end
717+
648718
test 'default key_format!' do
649719
Jbuilder.key_format camelize: :lower
650720
result = jbuild{ |json| json.camel_style 'for JS' }

0 commit comments

Comments
 (0)