How would I go about removing all empty elements (empty list items) from a nested Hash or YAML file?
Ruby's Hash#compact
, Hash#compact!
and Hash#delete_if!
do not work on nested nil
, empty?
and/or blank?
values. Note that the latter two methods are destructive, and that all nil
, ""
, false
, []
and {}
values are counted as blank?
.
Hash#compact
and Hash#compact!
are only available in Rails, or Ruby version 2.4.0 and above.
Here's a non-destructive solution that removes all empty arrays, hashes, strings and nil
values, while keeping all false
values:
(blank?
can be replaced with nil?
or empty?
as needed.)
def remove_blank_values(hash)
hash.each_with_object({}) do |(k, v), new_hash|
unless v.blank? && v != false
v.is_a?(Hash) ? new_hash[k] = remove_blank_values(v) : new_hash[k] = v
end
end
end
A destructive version:
def remove_blank_values!(hash)
hash.each do |k, v|
if v.blank? && v != false
hash.delete(k)
elsif v.is_a?(Hash)
hash[k] = remove_blank_values!(v)
end
end
end
Or, if you want to add both versions as instance methods on the Hash
class:
class Hash
def remove_blank_values
self.each_with_object({}) do |(k, v), new_hash|
unless v.blank? && v != false
v.is_a?(Hash) ? new_hash[k] = v.remove_blank_values : new_hash[k] = v
end
end
end
def remove_blank_values!
self.each_pair do |k, v|
if v.blank? && v != false
self.delete(k)
elsif v.is_a?(Hash)
v.remove_blank_values!
end
end
end
end
Other options:
v.blank? && v != false
with v.nil? || v == ""
to strictly remove empty strings and nil
valuesv.blank? && v != false
with v.nil?
to strictly remove nil
valuesEDITED 2017/03/15 to keep false
values and present other options
You can use Hash#reject to remove empty key/value pairs from a ruby Hash.
# Remove empty strings
{ a: 'first', b: '', c: 'third' }.reject { |key,value| value.empty? }
#=> {:a=>"first", :c=>"third"}
# Remove nil
{a: 'first', b: nil, c: 'third'}.reject { |k,v| v.nil? }
# => {:a=>"first", :c=>"third"}
# Remove nil & empty strings
{a: '', b: nil, c: 'third'}.reject { |k,v| v.nil? || v.empty? }
# => {:c=>"third"}
I believe it would be best to use a self recursive method. That way it goes as deep as is needed. This will delete the key value pair if the value is nil or an empty Hash.
class Hash
def compact
delete_if {|k,v| v.is_a?(Hash) ? v.compact.empty? : v.nil? }
end
end
Then using it will look like this:
x = {:a=>{:b=>2, :c=>3}, :d=>nil, :e=>{:f=>nil}, :g=>{}}
# => {:a=>{:b=>2, :c=>3}, :d=>nil, :e=>{:f=>nil}, :g=>{}}
x.compact
# => {:a=>{:b=>2, :c=>3}}
To keep empty hashes you can simplify this to.
class Hash
def compact
delete_if {|k,v| v.compact if v.is_a?(Hash); v.nil? }
end
end
Use hsh.delete_if. In your specific case, something like: hsh.delete_if { |k, v| v.empty? }
If you are using Rails
(or a standalone ActiveSupport
), starting from version 6.1
, there is a compact_blank method which removes blank
values from hashes.
It uses Object#blank? under the hood for determining if an item is blank.
{ a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
# => { b: 1, f: true }
Here is a link to the docs and a link to the relative PR.
A destructive variant is also available. See Hash#compact_blank!.
If you need to remove only nil
values,
please, consider using Ruby build-in Hash#compact and Hash#compact! methods.
{ a: 1, b: false, c: nil }.compact
# => { a: 1, b: false }
You could add a compact method to Hash like this
class Hash
def compact
delete_if { |k, v| v.nil? }
end
end
or for a version that supports recursion
class Hash
def compact(opts={})
inject({}) do |new_hash, (k,v)|
if !v.nil?
new_hash[k] = opts[:recurse] && v.class == Hash ? v.compact(opts) : v
end
new_hash
end
end
end