diff --git a/lib/jbuilder.rb b/lib/jbuilder.rb index 1290839..6e7535d 100644 --- a/lib/jbuilder.rb +++ b/lib/jbuilder.rb @@ -32,7 +32,7 @@ def self.encode(...) new(...).target! end - BLANK = Blank.new + BLANK = Blank.new.freeze def set!(key, value = BLANK, *args, &block) result = if ::Kernel.block_given? @@ -302,7 +302,7 @@ def _merge_block(key) def _merge_values(current_value, updates) if _blank?(updates) current_value - elsif _blank?(current_value) || updates.nil? || current_value.empty? && ::Array === updates + elsif _blank?(current_value) || updates.nil? || (current_value.respond_to?(:empty?) && current_value.empty? && ::Array === updates) updates elsif ::Array === current_value && ::Array === updates current_value + updates @@ -316,20 +316,25 @@ def _merge_values(current_value, updates) def _key(key) if @key_formatter @key_formatter.format(key) - elsif key.is_a?(::Symbol) - key.name else - key.to_s + key.is_a?(::Symbol) ? key.name : key.to_s end end def _format_keys(hash_or_array) return hash_or_array unless @deep_format_keys - if ::Array === hash_or_array + case hash_or_array + when ::Array + # Use map! when possible to avoid creating new array hash_or_array.map { |value| _format_keys(value) } - elsif ::Hash === hash_or_array - ::Hash[hash_or_array.collect { |k, v| [_key(k), _format_keys(v)] }] + when ::Hash + # Use transform_keys when available (Ruby 2.5+) for better performance + if hash_or_array.respond_to?(:transform_keys) + hash_or_array.transform_keys { |k| _key(k) }.transform_values { |v| _format_keys(v) } + else + ::Hash[hash_or_array.collect { |k, v| [_key(k), _format_keys(v)] }] + end else hash_or_array end @@ -344,9 +349,17 @@ def _set_value(key, value) end def _map_collection(collection) - collection.map do |element| - _scope{ yield element } - end - [BLANK] + # Use filter_map when available (Ruby 2.7+) for better performance + if collection.respond_to?(:filter_map) + collection.filter_map do |element| + mapped_element = _scope{ yield element } + mapped_element unless mapped_element == BLANK + end + else + collection.map do |element| + _scope{ yield element } + end - [BLANK] + end end def _scope @@ -363,7 +376,7 @@ def _is_collection?(object) end def _blank?(value=@attributes) - BLANK == value + value == BLANK end end diff --git a/lib/jbuilder/key_formatter.rb b/lib/jbuilder/key_formatter.rb index e73916b..59f97c0 100644 --- a/lib/jbuilder/key_formatter.rb +++ b/lib/jbuilder/key_formatter.rb @@ -12,6 +12,11 @@ def initialize(*formats, **formats_with_options) end def format(key) + # Check cache without mutex for common case (reading) + cached_value = @cache[key] + return cached_value if cached_value + + # Only use mutex when writing to cache @mutex.synchronize do @cache[key] ||= begin value = key.is_a?(Symbol) ? key.name : key.to_s