Removing all empty elements from a hash / YAML?

124,663

Solution 1

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

Solution 2

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

Solution 3

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

Solution 4

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 }

Solution 5

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

Share:
124,663

Related videos on Youtube

Brian Jordan
Author by

Brian Jordan

Game and web developer in San Francisco. Curator of Coding for Interviews, weekly CS topic overview and programming interview practice problem.

Updated on June 19, 2021

Comments

  • Brian Jordan
    Brian Jordan almost 3 years

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

  • Daniel O'Hara
    Daniel O'Hara almost 14 years
    Recursive one: proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&l); nil) : v.empty? }; hsh.delete_if(&proc)
  • acw
    acw over 12 years
    I believe there is a typo in your otherwise correct answer: proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&proc); nil) : v.empty? }; hsh.delete_if(&proc)
  • Ismael
    Ismael over 10 years
    compact should only remove nils. Not falsy values
  • B Seven
    B Seven over 10 years
    It would be great if #compact was added to Hash.
  • wdspkr
    wdspkr about 10 years
    rails version, that also works with values of other types than Array, Hash, or String (like Fixnum): swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.blank? }
  • dgilperez
    dgilperez over 9 years
    @BSeven it seems they heard you! api.rubyonrails.org/classes/Hash.html#method-i-compact (Rails 4.1)
  • Hertzel Guinness
    Hertzel Guinness almost 9 years
    careful, blank? goes for empty strings as well
  • Hertzel Guinness
    Hertzel Guinness almost 9 years
    hmm. circular references could lead to infinite loop IIUC.
  • Jerrod
    Jerrod over 8 years
    This will throw a NoMethodError if v is nil.
  • Serhii Nadolynskyi
    Serhii Nadolynskyi over 8 years
    You can use .delete_if { |k, v| v.blank? }
  • SirRawlins
    SirRawlins over 7 years
    Nice and tidy, but probably worth noting that unlike the accepted answer the Rails extension isn't recursive?
  • tokland
    tokland over 7 years
    This has a problem: Hash#delete_if is a destructive operation, while compact methods don't modify the object. You can use Hash#reject. Or call the method Hash#compact!.
  • illusionist
    illusionist over 7 years
    FYI: .empty? throws error for numbers, so you can use .blank? in Rails
  • AlexITC
    AlexITC over 7 years
    Note that "when Hash then compact(val).empty?" should be "when Hash then val.compact.empty?"
  • aidan
    aidan over 6 years
    Please note that compact and compact! come standard in Ruby => 2.4.0, and Rails => 4.1. They are non-recursive though.
  • A moskal escaping from Russia
    A moskal escaping from Russia about 6 years
    It omits empty hashes.
  • user1519240
    user1519240 over 5 years
    The recursive version does not work with HashWithIndifferentAccess.. Check my version at stackoverflow.com/a/53958201/1519240
  • courtsimas
    courtsimas almost 5 years
    or simply hash.compact!
  • FireDragon
    FireDragon about 4 years
    I also find useful is the Hash.except(:key) method
  • Алексей Лещук
    Алексей Лещук almost 3 years
    Check out mine recursive implementation based on standard compact and transform_values below!