How to Recursively Remove All Keys with Empty Values from (Yaml) Hash

Removing all empty elements from a hash / YAML?

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

Recursively removing `nil` and empty values from hash

Just another implementation, written for fun & practice.

  • No monkey patching
  • Works on Hashes and Arrays
  • Can be used as a module function like: DeepCompact.deep_compact(hash)
  • Also a destructive target modifying variant: DeepCompact.deep_compact!(hash)
  • Can be used by extending on an existing object: { foo: nil }.extend(DeepCompact).deep_compact
  • Can be used via refinements: adding using DeepCompact to a file/class will bring deep_compact and deep_compact! to Hashes and Arrays for all code in that file/class.

Here's the module:

module DeepCompact
[Hash, Array].each do |klass|
refine klass do
def deep_compact
DeepCompact.deep_compact(self)
end

def deep_compact!
DeepCompact.deep_compact!(self)
end
end
end

def self.extended(where)
where.instance_exec do
def deep_compact
DeepCompact.deep_compact(self)
end

def deep_compact!
DeepCompact.deep_compact!(self)
end
end
end

def deep_compact(obj)
case obj
when Hash
obj.each_with_object({}) do |(key, val), obj|
new_val = DeepCompact.deep_compact(val)
next if new_val.nil? || (new_val.respond_to?(:empty?) && new_val.empty?)
obj[key] = new_val
end
when Array
obj.each_with_object([]) do |val, obj|
new_val = DeepCompact.deep_compact(val)
next if new_val.nil? || (new_val.respond_to?(:empty?) && new_val.empty?)
obj << val
end
else
obj
end
end
module_function :deep_compact

def deep_compact!(obj)
case obj
when Hash
obj.delete_if do |_, val|
val.nil? || (val.respond_to?(:empty?) && val.empty?) || DeepCompact.deep_compact!(val)
end
obj.empty?
when Array
obj.delete_if do |val|
val.nil? || (val.respond_to?(:empty?) && val.empty?) || DeepCompact.deep_compact!(val)
end
obj.empty?
else
false
end
end
module_function :deep_compact!
end

And then some examples on how to use it:

hsh = {
'hello' => [
'world',
{ 'and' => nil }
],
'greetings' => nil,
'salutations' => {
'to' => { 'you' => true, 'him' => 'yes', 'her' => nil },
'but_not_to' => nil
}
}

puts "Original:"
pp hsh
puts
puts "Non-destructive module function:"
pp DeepCompact.deep_compact(hsh)
puts
hsh.extend(DeepCompact)
puts "Non-destructive after hash extended:"
pp hsh.deep_compact
puts
puts "Destructive refinement for array:"
array = [hsh]
using DeepCompact
array.deep_compact!
pp array

And the output:

Original:
{"hello"=>["world", {"and"=>nil}],
"greetings"=>nil,
"salutations"=>
{"to"=>{"you"=>true, "him"=>"yes", "her"=>nil}, "but_not_to"=>nil}}

Non-destructive module function:
{"hello"=>["world"], "salutations"=>{"to"=>{"you"=>true, "him"=>"yes"}}}

Non-destructive after hash extended:
{"hello"=>["world"], "salutations"=>{"to"=>{"you"=>true, "him"=>"yes"}}}

Destructive refinement for array:
[{"hello"=>["world"], "salutations"=>{"to"=>{"you"=>true, "him"=>"yes"}}}]

Or just use one of the multiple gems that provide this for you.

Removing empty values from an deeply nested Ruby hash

Try the below with recusrion:

def remove_blank(hash)
hash.each do |_, value|
if value.is_a? Hash
remove_blank(value)
elsif value.is_a?(Array) && value[0].is_a?(Hash)
remove_blank(value[0])
end
end.reject! {|_, value| value.nil? || (value.is_a?(Array) && value.reject(&:empty?).empty?) }
end

remove the key and value in the params hash with ruby on rails

class Hash
def compact(opts={})
inject({}) do |new_hash, (k,v)|
if !v.blank?
new_hash[k] = opts[:recursive] && v.class == Hash ? v.compact(opts) : v
end
new_hash
end
end
end

hash = {
:first_name=> {
1=> "david",
2=> ""
},
:last_name=> {
1=> "david",
2=> ""
},
:role=> {
1=> "dev",
2=> ""
},
:bio=> {
1=> "commercial",
2=> ""
}
}

hash.compact(:recursive=>true)

will give

{
:first_name => {
1 => "david"
},
:last_name => {
1 => "david"
},
:role => {
1 => "dev"
},
:bio => {
1 => "commercial"
}
}

source: Removing all empty elements from a hash / YAML?

How do I recursively flatten a YAML file into a JSON object where keys are dot separated strings?

Since the question is about using YAML files for i18n on a Rails app, it's worth noting that the i18n gem provides a helper module I18n::Backend::Flatten that flattens translations exactly like this:

test.rb:

require 'yaml'
require 'json'
require 'i18n'

yaml = YAML.load <<YML
en:
questions:
new: 'New Question'
other:
recent: 'Recent'
old: 'Old'
YML
include I18n::Backend::Flatten
puts JSON.pretty_generate flatten_translations(nil, yaml, nil, false)

Output:

$ ruby test.rb
{
"en.questions.new": "New Question",
"en.questions.other.recent": "Recent",
"en.questions.other.old": "Old"
}

Remove duplicate keys from a hash of hashes and arrays (and ensure any resulting empty hashs are also removed)

I think this does what you want:

#!/usr/bin/perl
use warnings;
use strict;
use feature qw/say/;
use JSON::XS; # Better than JSON; also see JSON::MaybeXS

my $j = <<EOJSON;
{
"foo": 1,
"bar": {
"foo": true,
"baz": false
},
"dog": "woof",
"cat": [ { "foo": 3 } ]
}
EOJSON

sub count_keys {
my ($j, $seen) = @_;
my $type = ref $j;
if ($type eq "ARRAY") {
count_keys($_, $seen) for @$j;
} elsif ($type eq "HASH") {
while (my ($key, $val) = each %$j) {
$seen->{$key}++;
count_keys($val, $seen) if ref $val;
}
}
return $seen;
}

sub remove_dups {
my ($j, $seen) = @_;
$seen //= count_keys($j, {});

my $type = ref $j;
if ($type eq "ARRAY") {
return [ map { remove_dups($_, $seen) } @$j ];
} elsif ($type eq "HASH") {
my %obj = %$j;
delete @obj{grep { $seen->{$_} > 1 } keys %obj};
while (my ($key, $val) = each %obj) {
$obj{$key} = remove_dups($val, $seen) if ref $val;
}
return \%obj;
} else {
return $j;
}
}

my $parsed = decode_json $j;
my $printer = JSON::XS->new->pretty->canonical;
say "Before:";
print $printer->encode($parsed);
say "After:";
my $dedup = remove_dups $parsed;
print $printer->encode($dedup);

produces

Before:
{
"bar" : {
"baz" : false,
"foo" : true
},
"cat" : [
{
"foo" : 3
}
],
"dog" : "woof",
"foo" : 1
}
After:
{
"bar" : {
"baz" : false
},
"cat" : [
{}
],
"dog" : "woof"
}

Edit for explanation:

The first time remove_dups is called on a perl data structure representing a json value (Which doesn't have to be a json object), it calls count_keys to recursively walk the structure and create a hash of all the keys and the number of times each one occurs. Then it again recursively walks the structure, returning a deep copy without keys that appeared more than once in the original.

This line is the real magic:

delete @obj{grep { $seen->{$_} > 1 } keys %obj};

It uses a hash slice to delete a bunch of keys all at once, with the grep bit returning a list of keys that appeared more than once. More information on slices.

How to iterate over deep nested hash without known depth in Ruby

So, you want to parse recursively until there are no more levels to parse into.

It’s super common in software and referred to as “recursion”. Have a search around to learn more about it - it’ll come up again and again in your journey. Welcome to ruby btw!

As for your actual current problem. Have a read of https://mrxpalmeiras.wordpress.com/2017/03/30/how-to-parse-a-nested-yaml-config-file-in-python-and-ruby/

But also, consider the i18n gem. See this answer https://stackoverflow.com/a/51216931/1777331 and the docs for the gem https://github.com/ruby-i18n/i18n This might fix your problem of handling internationalisation without you having to get into the details of handling yaml files.

All keys from hash with matching array values in Rails 4

Try this one

CHAR_MAP.keys.select { |k| (CHAR_MAP[k] - x).empty? }

How do I fetch multiple hash keys with a nested hash?

I presume you want to grab the values out in a tuple? You can make an array that contains whatever collection of values you want.

Try the following for name and city:

[imahash[:id][:name], imahash[:location][:city]]
=> ["Alma", "Freeport"]


Related Topics



Leave a reply



Submit