ruby-on-rails ruby hash yaml

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 con v.nil? || v == "" v.nil? || v == "" v.nil? || v == "" para eliminar estrictamente cadenas vacías y valores nil
  • Reemplazar v.blank? && v != false v.blank? && v != false con v.nil? para eliminar estrictamente valores nil
  • 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