Given a hash like this:
{ id: 1, name: "test", children: [ { id: 1, name: "kid 1" }, { id: 2, name: "kid 2" } ] } How can I remove all id keys recursively?
You can write a function to recursively traverse Hashes and Arrays.
def delete_recursively(thing, key_to_delete) case thing when Hash # Delete the key thing.delete(key_to_delete) # Recurse into each remaining hash value. thing.each_value do |value| delete_recursively(value, key_to_delete) end when Array # Recurse into each value of the array. thing.each do |value| delete_recursively(value, key_to_delete) end end end This can be expanded to include other data types as necessary.
This of course calls for a recursive solution. The following method does not mutate the original hash.
Code
def recurse(obj, key_to_delete) case obj when Array obj.map { |e| recurse(e, key_to_delete) } else # hash obj.reject { |k,_| k == key_to_delete }.transform_values do |v| case v when Hash, Array recurse(v, key_to_delete) else v end end end end Example
h = { id: 1, name: "test", children: [ { id: 1, name: "kid 1" }, { id: 2, name: "kid 2", grandkids: [ { id: 3, name: "gkid1" }] } ] } recurse(h, :id) #=> { :name=>"test", :children=>[ # {:name=>"kid 1"}, {:name=>"kid 2", :grandkids=>[{:name=>"gkid1"}]} # ] # } Explanation
The operations performed by recursive methods are always difficult to explain. In my experience the best way is to salt the code with puts statements. However, that in itself is not enough because when viewing output it is difficult to keep track of the level of recursion at which particular results are obtained and either passed to itself or returned to a version of itself. The solution to that is to indent and un-indent results, which is what I've done below. Note the way I've structured the code and the few helper methods I use are fairly general-purpose, so this approach can be adapted to examine the operations performed by other recursive methods.
INDENT = 8 @col = -INDENT def indent; @col += INDENT; end def undent; @col -= INDENT; end def pu(s); print " "*@col; puts s; end def puhline; pu('-'*(70-@col)); end def recurse(obj, key_to_delete) begin # rem indent # rem puhline # rem pu "passed obj = #{obj}, key_to_delete = #{key_to_delete}" # rem case obj when Array pu "processing Array..." # rem obj.map do |e| pu " calling recurse(#{e}, #{key_to_delete})..." # rem recurse(e, key_to_delete) end else # hash obj.reject { |k,_| k == :id }. tap { |h| pu " obj with :id removed=#{h}" }. # rem transform_values do |v| pu " in tranform_values, v=#{v}" # rem case v when Hash, Array pu " v is Hash or Arrary" # rem pu " calling recurse(#{v}, #{key_to_delete})..." # rem recurse(v, key_to_delete) else pu " keeping the literal v" # rem v end end end.tap { |obj| pu "returning #{obj}" } ensure puhline # rem undent end end recurse(h, :id) ---------------------------------------------------------------------- passed obj = {:id=>1, :name=>"test", :children=>[{:id=>1, :name=>"kid 1"}, {:id=>2, :name=>"kid 2", :grandkids=>[{:id=>3, :name=>"gkid1"}]}]}, key_to_delete = id obj with :id removed={:name=>"test", :children=>[{:id=>1, :name=>"kid 1"}, {:id=>2, :name=>"kid 2", :grandkids=>[{:id=>3, :name=>"gkid1"}]}]} in tranform_values, v=test keeping the literal v in tranform_values, v=[{:id=>1, :name=>"kid 1"}, {:id=>2, :name=>"kid 2", :grandkids=>[{:id=>3, :name=>"gkid1"}]}] v is Hash or Arrary calling recurse([{:id=>1, :name=>"kid 1"}, {:id=>2, :name=>"kid 2", :grandkids=>[{:id=>3, :name=>"gkid1"}]}], id)... -------------------------------------------------------------- passed obj = [{:id=>1, :name=>"kid 1"}, {:id=>2, :name=>"kid 2", :grandkids=>[{:id=>3, :name=>"gkid1"}]}], key_to_delete = id processing Array... calling recurse({:id=>1, :name=>"kid 1"}, id)... ------------------------------------------------------ passed obj = {:id=>1, :name=>"kid 1"}, key_to_delete = id obj with :id removed={:name=>"kid 1"} in tranform_values, v=kid 1 keeping the literal v returning {:name=>"kid 1"} ------------------------------------------------------ calling recurse({:id=>2, :name=>"kid 2", :grandkids=>[{:id=>3, :name=>"gkid1"}]}, id)... ------------------------------------------------------ passed obj = {:id=>2, :name=>"kid 2", :grandkids=> [{:id=>3, :name=>"gkid1"}]}, key_to_delete = id obj with :id removed={:name=>"kid 2", :grandkids=> [{:id=>3, :name=>"gkid1"}]} in tranform_values, v=kid 2 keeping the literal v in tranform_values, v=[{:id=>3, :name=>"gkid1"}] v is Hash or Arrary calling recurse([{:id=>3, :name=>"gkid1"}], id)... ---------------------------------------------- passed obj = [{:id=>3, :name=>"gkid1"}], key_to_delete = id processing Array... calling recurse({:id=>3, :name=>"gkid1"}, id)... -------------------------------------- passed obj = {:id=>3, :name=>"gkid1"}, key_to_delete = id obj with :id removed={:name=>"gkid1"} in tranform_values, v=gkid1 keeping the literal v returning {:name=>"gkid1"} -------------------------------------- returning [{:name=>"gkid1"}] ---------------------------------------------- returning {:name=>"kid 2", :grandkids=>[{:name=>"gkid1"}]} ------------------------------------------------------ returning [{:name=>"kid 1"}, {:name=>"kid 2", :grandkids=>[{:name=>"gkid1"}]}] -------------------------------------------------------------- returning {:name=>"test", :children=>[{:name=>"kid 1"}, {:name=>"kid 2", :grandkids=>[{:name=>"gkid1"}]}]} ---------------------------------------------------------------------- #=> {:name=>"test", :children=>[{:name=>"kid 1"}, {:name=>"kid 2", :grandkids=>[{:name=>"gkid1"}]}]}