How to remove nested keys from a hash list in Rails
Asked Answered
O

5

9

I am now trying for some hours to remove a nested hash key of a hash list. I saw many solution non-nested hashs wich looks like this:

   sample_hash = {"key1" => "value1", "key2" => "value2"}
   sample_hash.except("key1") 

This results in:

  {"key2"=>"value2"}

But if I try to use the except method on a hash with nested key then it doesn't work. Here my code:

  nested_hash = {"key1"=>"value1", "key2"=>{
                                           "nested_key1"=>"nestedvalue1",
                                           "nested_key2"=>"nestedvalue2"
                                           }
                }

  nested_hash.except("nested_key2")

The except() method returns the nested_hash without any changes. I have looked for a solution how I can pass nested hash-keys to the except method, but couldn't find anything. Is it even possible to pass nested keys to this method or should I use some other method which deletes a nested hash key from my hash list?

Overmaster answered 28/5, 2013 at 17:2 Comment(1)
Perhaps you want a real tree, rather than a nested hash. You might look at github.com/evolve75/RubyTree, github.com/stefankroes/ancestry, or github.com/mceachen/closure_tree for gems that might work for you.Crossquestion
I
9

what about

Hash[nested_hash.map {|k,v| [k,(v.respond_to?(:except)?v.except("nested_key2"):v)] }]

=> {"key1"=>"value1", "key2"=>{"nested_key1"=>"nestedvalue1"}}

ugh.

Ise answered 28/5, 2013 at 17:54 Comment(2)
This works great, but could you explain this notation? How is the keyword "Hash" taking in an array index, and why are we mapping each element to an array of [k,v] or [k,v.except("nested_key2")] ?Frodina
see ruby-doc.org/core-2.0/Hash.html#method-c-5B-5D , the standard method to create hashes. It accepts either a list of key,value,key,value,... or associations key=>value, key=>value,... or nested array, what we use here.Ise
C
7

The accepted solution is valid for the scenario given but if you're looking for something that will do this for arbitrarily nested hash tables then you're going to need a recursive solution. I couldn't find a suitable solution anywhere, so I wrote one here.

Reproduced here with annotations:

class Hash
  def except_nested(key)
    r = Marshal.load(Marshal.dump(self)) # deep copy the hashtable
    r.except_nested!(key)
  end

  def except_nested!(key)
    self.except!(key)
    self.each do |_, v| # essentially dfs traversal calling except!
      v.except_nested!(key) if v.is_a?(Hash)
    end
  end
end

adding it to the Hash class so that you can call it the same way you call except/except! anywhere else.

t = { a: '1', b: { c: '3', d: '4' } } 

r = t.except_nested(:c) 
# r => {:a=>"1", :b=>{:d=>"4"}}
# t => {:a=>"1", :b=>{:c=>"3", :d=>"4"}}

t.except_nested!(:c)
# t => {:a=>"1", :b=>{:d=>"4"}}
Carbonize answered 15/11, 2017 at 23:7 Comment(0)
F
0

try

my_hash = Hash[nested_hash.map {|k,v| {k=>v.is_a? Array ? v.except("nested_key2") : v}}.map {|key, value| [key, value]}]

But this seems wrong, I wish I never started down this path, I'm willing to bet there is an easier way!

Flora answered 28/5, 2013 at 17:15 Comment(1)
thx for your suggestion. but it throws a NoMethodError: undefined method `except' for "value1":StringOvermaster
T
0

If you know that the nested key will always be there then you can just do

nested_hash['key2'].except!('nested_key2')

the whole nested_hash will now be lacking 'nested_key2'

Tonsillectomy answered 15/9, 2022 at 0:28 Comment(0)
J
0

For certain edge cases, like arrays and deeply nested hashes, you can use this gem "except_nested" for all scenarios solution:

h = { name: "Jane", age: 20, preferences: { color: "red", pet: "cat", drink: "coffee" } }

h.except_nested(:age, preferences: [:color, :pet])
=> { name: "Jane", preferences: { drink: "coffee" } }
Jaan answered 3/10, 2023 at 10:10 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.