ruby on rails - ¿Eliminando todos los elementos vacíos de un hash/YAML?
ruby-on-rails (15)
¡Ruby''s Hash#compact
, Hash#compact!
y Hash#delete_if!
no funciona en nido anidado, empty?
y / o en blank?
valores. Tenga en cuenta que los dos últimos métodos son destructivos, y que todos los valores nil
, ""
, false
, []
y {}
se cuentan como en blank?
.
Hash#compact
y Hash#compact!
solo están disponibles en Rails, o Ruby versión 2.4.0 y superior.
Aquí hay una solución no destructiva que elimina todas las matrices vacías, hash, cadenas y valores nil
, al tiempo que mantiene todos false
valores false
:
( blank?
se puede reemplazar por nil?
o empty?
según sea necesario).
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
Una versión destructiva:
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
O bien, si desea agregar ambas versiones como métodos de instancia en la clase 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
Otras opciones:
- Reemplazar
v.blank? && v != false
v.blank? && v != false
conv.nil? || v == ""
v.nil? || v == ""
v.nil? || v == ""
para eliminar estrictamente cadenas vacías y valoresnil
- Reemplazar
v.blank? && v != false
v.blank? && v != false
conv.nil?
para eliminar estrictamente valoresnil
- Etc.
EDITADO 15/03/2017 para mantener valores false
y presentar otras opciones
¿Cómo podría eliminar todos los elementos vacíos (elementos de la lista vacía) de un archivo Hash o YAML anidado?
Creo que sería mejor usar un método auto recursivo. De esa forma va tan profundo como se necesita. Esto eliminará el par de valores clave si el valor es nulo o un Hash vacío.
class Hash
def compact
delete_if {|k,v| v.is_a?(Hash) ? v.compact.empty? : v.nil? }
end
end
Entonces usarlo se verá así:
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}}
Para mantener hashes vacíos puedes simplificar esto.
class Hash
def compact
delete_if {|k,v| v.compact if v.is_a?(Hash); v.nil? }
end
end
En Simple one liner para eliminar valores nulos en Hash,
rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? }
Este eliminaría hashes vacíos también:
swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.empty? }
hsh.delete_if &swoop
Hice un método deep_compact para esto que recursivamente filtra los registros nulos (y opcionalmente, los registros en blanco también):
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
Intenta esto para eliminar nada
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}
Podría agregar un método compacto a Hash como este
class Hash
def compact
delete_if { |k, v| v.nil? }
end
end
o para una versión que sea compatible con la recursión
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
Podría hacerse con la biblioteca de facets (las características que faltan de la biblioteca estándar), así:
require ''hash/compact''
require ''enumerable/recursively''
hash.recursively { |v| v.compact! }
Funciona con cualquier Enumerable (incluido Array, Hash).
Mira cómo se implementa el método recursivo .
Puede usar Hash#reject para eliminar los pares de clave / valor vacíos de un hash de ruby.
# 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"}
Rails 4.1 agregó api.rubyonrails.org/classes/Hash.html#method-i-compact y Hash#compact! como una extensión del núcleo de la clase Hash
de Ruby. Puedes usarlos así:
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: esta implementación no es recursiva. Como curiosidad, lo implementaron usando #select
lugar de #delete_if
por motivos de rendimiento. Vea aquí para el punto de referencia .
En caso de que quiera respaldarlo en su aplicación 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
Sé que este hilo es un poco viejo, pero se me ocurrió una solución mejor que admite hashes multidimensionales. Utiliza delete_if? excepto que es multidimensional y borra todo lo que tiene un valor vacío por defecto y si se pasa un bloque se pasa a través de sus hijos.
# 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
Use hsh.delete_if . En su caso específico, algo como: hsh.delete_if { |k, v| v.empty? }
hsh.delete_if { |k, v| v.empty? }
funciona tanto para hashes como para arreglos
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
PS basado en la respuesta de alguien, no puedo encontrar
nuestra versión: también limpia las cadenas vacías y los valores nulos
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
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