I have been trying to get rid of all hash keys in my YAML file that have empty (blank) values or empty hashes as values.
This earlier post helped me to get it almost
I know this thread is a bit old but I came up with a better solution which supports Multidimensional hashes. It uses delete_if? except its multidimensional and cleans out anything with a an empty value by default and if a block is passed it is passed down through it's children.
# Hash cleaner
class Hash
def clean!
self.delete_if do |key, val|
if block_given?
yield(key,val)
else
# Prepeare the tests
test1 = val.nil?
test2 = val === 0
test3 = val === false
test4 = val.empty? if val.respond_to?('empty?')
test5 = val.strip.empty? if val.is_a?(String) && val.respond_to?('empty?')
# Were any of the tests true
test1 || test2 || test3 || test4 || test5
end
end
self.each do |key, val|
if self[key].is_a?(Hash) && self[key].respond_to?('clean!')
if block_given?
self[key] = self[key].clean!(&Proc.new)
else
self[key] = self[key].clean!
end
end
end
return self
end
end
Here's a more generic method:
class Hash
def deep_reject(&blk)
self.dup.deep_reject!(&blk)
end
def deep_reject!(&blk)
self.each do |k, v|
v.deep_reject!(&blk) if v.is_a?(Hash)
self.delete(k) if blk.call(k, v)
end
end
end
{ a: 1, b: nil, c: { d: nil, e: '' } }.deep_reject! { |k, v| v.blank? }
==> { a: 1 }
Just a bit related thing. If you want to delete specified keys from nested hash:
def find_and_destroy(*keys)
delete_if{ |k, v| (keys.include?(k.to_s) ? true : ( (v.each { |vv| vv = vv.find_and_destroy(*keys) }) if v.instance_of?(Array) ; (v.each { |vv| vv = vv.find_and_destroy(*keys) }) if v.instance_of?(Hash); false) )}
end
.You can also customize it further
I think this the most correct version:
h = {a: {b: {c: "",}, d:1}, e:2, f: {g: {h:''}}}
p = proc do |_, v|
v.delete_if(&p) if v.respond_to? :delete_if
v.nil? || v.respond_to?(:"empty?") && v.empty?
end
h.delete_if(&p)
#=> {:a=>{:d=>1}, :e=>2}
hash = {"x"=>{"m"=>{"n"=>{}}}, 'y' => 'content'}
clean = proc{ |k,v| !v.empty? ? Hash === v ? v.delete_if(&clean) : false : true }
hash.delete_if(&clean)
#=> {"y"=>"content"}
or like @sawa suggested, you can use this proc
clean = proc{ |k,v| v.empty? or Hash === v && v.delete_if(&clean) }
class Hash
def delete_blank
delete_if{|k, v| v.empty? or v.instance_of?(Hash) && v.delete_blank.empty?}
end
end
p hash.delete_blank
# => {"y"=>"content"}