从散列/YAML 中移除所有空元素?

如何从嵌套的 Hash 或 YAML 文件中删除所有空元素(空列表项) ?

129426 次浏览

使用 Delete _ if。在您的特定情况下,类似于: hsh.delete_if { |k, v| v.empty? }

这个也会删除空散列:

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

我知道这个线程有点老,但我想出了一个更好的解决方案,支持多维散列。它使用 delete _ if?除了它的多维数据,并且在默认情况下清除任何带有空值的数据,如果传递了一个块,它将通过它的子元素向下传递。

# 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

您可以像这样向 Hash 添加一个压缩方法

class Hash
def compact
delete_if { |k, v| v.nil? }
end
end

或者支持递归的版本

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

我们的版本: 它还会清除空字符串和 nil 值

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

Rails 4.1添加了 哈希 # 紧凑型哈希 # 紧凑型!作为 Ruby 的 Hash类的核心扩展:

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
# => {}

注意: 这个实现不是递归的。出于好奇心,出于性能原因,他们使用 #select而不是 #delete_if来实现它。见 这里是基准

如果你想将它反向移植到你的 Rails 3应用程序:

# 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

在“简单一行程序”中,用于删除 Hash 中的空值,

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

我认为最好使用自递归方法。这样才能深入到需要的地方。这将删除键值对,如果值为空或为空散列。

class Hash
def compact
delete_if {|k,v| v.is_a?(Hash) ? v.compact.empty? : v.nil? }
end
end

然后使用它会看起来像这样:

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

要保持空散列,可以将其简化为。

class Hash
def compact
delete_if {|k,v| v.compact if v.is_a?(Hash); v.nil? }
end
end

我为此创建了一个 deep _ close 方法,它递归地过滤出 nil 记录(还可以选择过滤空白记录) :

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

可以使用 Hash # 拒绝从 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"}
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

可以使用 方面库(标准库中缺少的特性) ,如下所示:

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

使用任何枚举(包括数组、哈希)。

看看 递归方法递归方法是如何实现的。

Ruby 的 Hash#compactHash#compact!Hash#delete_if!不能在嵌套的 nilempty?和/或 blank?值上工作。请注意,后两种方法是破坏性的,所有的 nil""false[]Hash#compact!0值都计为 blank?

Hash#compactHash#compact!只能在 Rails 或 Ruby 2.4.0及以上版本中使用。

下面是一个非破坏性的解决方案,它删除所有的空数组、哈希、字符串和 nil值,同时保留所有的 false值:

(如有需要,可以用 nil?empty?代替 blank?。)

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

一个毁灭性的版本:

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

或者,如果希望在 Hash类上将两个版本都作为实例方法添加:

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

其他选择:

  • v.nil? || v == ""替换 v.blank? && v != false,以严格删除空字符串和 nil
  • v.nil?替换 v.blank? && v != false,以严格删除 nil
  • 等等。

编辑2017/03/15保持 false值并提出其他选项

尝试这样删除空

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}

同时适用于散列和数组

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

附注: 根据某人的回答,找不到

用途 -Helpers::RecursiveCompact.recursive_compact(something)

https://stackoverflow.com/a/14773555/1519240的递归版本可以工作,但是不能用于 HashWithIndifferentAccess或其他类似 Hash 的类。

下面是我正在使用的版本:

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)将接受更多类似 Hash 的类。

如果希望同时使用符号和字符串访问新散列,也可以将 inject({})替换为 inject(HashWithIndifferentAccess.new)

这是我的东西:

# 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

如果使用 Ruby 2.4 + ,可以调用 compactcompact!

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

从散列中深度删除空值。

  # 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

判断是否有效(Rails 6.1 +)

如果您使用的是 Rails(或独立的 ActiveSupport) ,则从版本 6.1开始,有一个从散列中删除 blank值的 compact_blank方法。

它使用引擎盖下的 Object#blank?来确定一个项目是否为空白。

{ a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
# => { b: 1, f: true }

这是 链接到文件链接到相关的公关

一种破坏性的变体也是可用的。参见 Hash#compact_blank!


如果只需要删除 nil值,

请考虑使用 Ruby 内置的 Hash#compactHash#compact!方法。

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

在 ruby 2.7中有标准的 compacttransform_values方法,在 Hash 中,你可以这样做:

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

这是最简洁的实现方式了,亲爱的。

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