Brian Jordan
Brian Jordan

Reputation: 2407

Removing all empty elements from a hash / YAML?

How would I go about removing all empty elements (empty list items) from a nested Hash or YAML file?

Upvotes: 166

Views: 137036

Answers (22)

Osvaldo Perez
Osvaldo Perez

Reputation: 27

  validated_params
    .to_h
    .delete_if {|_,v| !v.present? }

Upvotes: -1

in ruby 2.7 there are standard compact and transform_values methods, on the Hash so you can do this like this:

class Hash 
  def deep_compact
    compact.transform_values{|vl| vl.is_a?(Hash) ? vl.deep_compact : vl }
  end
end

It's the neatiest implementation, imho.

Upvotes: 0

Marian13
Marian13

Reputation: 9238

compact_blank (Rails 6.1+)

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 }

Upvotes: 52

Miro Mudrik
Miro Mudrik

Reputation: 7

Deep deletion nil values from a hash.

  # returns new instance of hash with deleted nil values
  def self.deep_remove_nil_values(hash)
    hash.each_with_object({}) do |(k, v), new_hash|
      new_hash[k] = deep_remove_nil_values(v) if v.is_a?(Hash)
      new_hash[k] = v unless v.nil?
    end
  end

  # rewrite current hash
  def self.deep_remove_nil_values!(hash)
    hash.each do |k, v|
      deep_remove_nil_values(v) if v.is_a?(Hash)
      hash.delete(k) if v.nil?
    end
  end

Upvotes: 0

Wilson Silva
Wilson Silva

Reputation: 11395

If you're using Ruby 2.4+, you can call compact and compact!

h = { a: 1, b: false, c: nil }
h.compact! #=> { a: 1, b: false }

https://ruby-doc.org/core-2.4.0/Hash.html#method-i-compact-21

Upvotes: 13

srghma
srghma

Reputation: 5323

works for both hashes and arrays

module Helpers
  module RecursiveCompact
    extend self

    def recursive_compact(hash_or_array)
      p = proc do |*args|
        v = args.last
        v.delete_if(&p) if v.respond_to? :delete_if
        v.nil? || v.respond_to?(:"empty?") && v.empty?
      end

      hash_or_array.delete_if(&p)
    end
  end
end

P.S. based on someones answer, cant find

usage - Helpers::RecursiveCompact.recursive_compact(something)

Upvotes: 6

Varun Garg
Varun Garg

Reputation: 2654

Here is something I have:

# recursively remove empty keys (hashes), values (array), hashes and arrays from hash or array
def sanitize data
  case data
  when Array
    data.delete_if { |value| res = sanitize(value); res.blank? }
  when Hash
    data.delete_if { |_, value| res = sanitize(value); res.blank? }
  end
  data.blank? ? nil : data
end

Upvotes: 0

user1519240
user1519240

Reputation: 2354

The recursive version of https://stackoverflow.com/a/14773555/1519240 works, but not with HashWithIndifferentAccess or other classes that are kind of Hash..

Here is the version I am using:

def recursive_compact
  inject({}) do |new_hash, (k,v)|
    if !v.nil?
      new_hash[k] = v.kind_of?(Hash) ? v.recursive_compact : v
    end
    new_hash
  end
end

kind_of?(Hash) will accept more classes that are like a Hash.

You can also replace inject({}) by inject(HashWithIndifferentAccess.new) if you want to access the new hash using both symbol and string.

Upvotes: 1

Rahul Patel
Rahul Patel

Reputation: 1424

Try this to remove nil

hash = { a: true, b: false, c: nil }
=> {:a=>true, :b=>false, :c=>nil}
hash.inject({}){|c, (k, v)| c[k] = v unless v.nil?; c}
=> {:a=>true, :b=>false}

Upvotes: 0

dgilperez
dgilperez

Reputation: 10796

Rails 4.1 added Hash#compact and Hash#compact! as a core extensions to Ruby's Hash class. You can use them like this:

hash = { a: true, b: false, c: nil }
hash.compact                        
# => { a: true, b: false }
hash                                
# => { a: true, b: false, c: nil }
hash.compact!                        
# => { a: true, b: false }
hash                                
# => { a: true, b: false }
{ c: nil }.compact                  
# => {}

Heads up: this implementation is not recursive. As a curiosity, they implemented it using #select instead of #delete_if for performance reasons. See here for the benchmark.

In case you want to backport it to your Rails 3 app:

# config/initializers/rails4_backports.rb

class Hash
  # as implemented in Rails 4
  # File activesupport/lib/active_support/core_ext/hash/compact.rb, line 8
  def compact
    self.select { |_, value| !value.nil? }
  end
end

Upvotes: 182

Sebastian Jay
Sebastian Jay

Reputation: 61

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:

  • Replace v.blank? && v != false with v.nil? || v == "" to strictly remove empty strings and nil values
  • Replace v.blank? && v != false with v.nil? to strictly remove nil values
  • Etc.

EDITED 2017/03/15 to keep false values and present other options

Upvotes: 4

Chix
Chix

Reputation: 1

class Hash   
  def compact
    def _empty?(val)
      case val
      when Hash     then val.compact.empty?
      when Array    then val.all? { |v| _empty?(v) }
      when String   then val.empty?
      when NilClass then true
      # ... custom checking 
      end
    end

    delete_if { |_key, val| _empty?(val) }   
  end 
end

Upvotes: 0

Dmitry Polushkin
Dmitry Polushkin

Reputation: 3393

Could be done with facets library (a missing features from standard library), like that:

require 'hash/compact'
require 'enumerable/recursively'
hash.recursively { |v| v.compact! }

Works with any Enumerable (including Array, Hash).

Look how recursively method is implemented.

Upvotes: 3

smd1000
smd1000

Reputation: 313

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"}

Upvotes: 7

mwalsher
mwalsher

Reputation: 2790

I made a deep_compact method for this that recursively filters out nil records (and optionally, blank records as well):

class Hash
  # Recursively filters out nil (or blank - e.g. "" if exclude_blank: true is passed as an option) records from a Hash
  def deep_compact(options = {})
    inject({}) do |new_hash, (k,v)|
      result = options[:exclude_blank] ? v.blank? : v.nil?
      if !result
        new_value = v.is_a?(Hash) ? v.deep_compact(options).presence : v
        new_hash[k] = new_value if new_value
      end
      new_hash
    end
  end
end

Upvotes: 4

6ft Dan
6ft Dan

Reputation: 2435

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

Upvotes: 0

ramya
ramya

Reputation: 275

In Simple one liner for deleting null values in Hash,

rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? } 

Upvotes: 3

sahin
sahin

Reputation: 31

our version: it also cleans the empty strings and nil values

class Hash

  def compact
    delete_if{|k, v|

      (v.is_a?(Hash) and v.respond_to?('empty?') and v.compact.empty?) or
          (v.nil?)  or
          (v.is_a?(String) and v.empty?)
    }
  end

end

Upvotes: 3

opsb
opsb

Reputation: 30211

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

Upvotes: 74

jpemberthy
jpemberthy

Reputation: 7533

Use hsh.delete_if. In your specific case, something like: hsh.delete_if { |k, v| v.empty? }

Upvotes: 148

Kelly Becker
Kelly Becker

Reputation: 91

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

Upvotes: 4

punund
punund

Reputation: 4421

This one would delete empty hashes too:

swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash);  v.empty? }
hsh.delete_if &swoop

Upvotes: 7

Related Questions