24

I have a hash like:

h = {'name' => 'sayuj', 
     'age' => 22, 
     'project' => {'project_name' => 'abc', 
                   'duration' => 'prq'}}

I need a dup of this hash, the change should not affect the original hash.

When I try,

d = h.dup # or d = h.clone
d['name'] = 'sayuj1'
d['project']['duration'] = 'xyz'

p d #=> {"name"=>"sayuj1", "project"=>{"duration"=>"xyz", "project_name"=>"abc"}, "age"=>22}
p h #=> {"name"=>"sayuj", "project"=>{"duration"=>"xyz", "project_name"=>"abc"}, "age"=>22}

Here you can see the project['duration'] is changed in the original hash because project is another hash object.

I want the hash to be duped or cloned recursively. How can I achieve this?

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
Sayuj
  • 7,464
  • 13
  • 59
  • 76

5 Answers5

40

Here's how you make deep copies in Ruby

d = Marshal.load( Marshal.dump(h) )
Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • 2
    This creates full copies of all objects referenced by `h`. This might be exactly what is needed by Sayuj for simple String hashes. With more complex objects, this might not be desired anymore. Once could override the `Hash#dup` method to dup all hashes in `values` recursively. But that would need to be extended for every object type. – Holger Just Jan 03 '12 at 10:21
  • 2
    @HolgerJust: yes, that's why it's called a "deep copy" :-) – Sergio Tulentsev Jan 03 '12 at 10:22
  • 1
    Of course. I just wanted to mention that it might do more than the OP intended (although it's probably just fine) :) So it's just for, well, future reference. – Holger Just Jan 03 '12 at 10:25
  • 2
    Note that this will not work when there is a default proc (e.g. `h = Hash.new {|h,k| h[k] = 1}`) – Mark Thomas Jan 03 '12 at 12:44
3

If you are in Rails: Hash.deep_dup

fguillen
  • 36,125
  • 23
  • 149
  • 210
2

In case the Marchal #dump/load pair isn't work, for there is a Hash's method #deep_dup, so you can:

h = {'name' => 'sayuj', 
 'age' => 22, 
 'project' => {'project_name' => 'abc', 
               'duration' => 'prq'}}

h1 = h.deep_dup
Малъ Скрылевъ
  • 16,187
  • 5
  • 56
  • 69
1

This is an answer to a reasonably old question, but I happened upon it while implementing something similar, thought I'd chime in for a more efficient method.

For the simple, two level deep hash like above, you can also do something like this:

d = h.inject({}) {|copy, (key, value)| 
    copy[key] = value.dup rescue value; copy
}

I ran a test on a hash of hashes with 4k elements, each a few hundred bytes, and it was about 50% faster than the Marshal.dump/load

Of course, it's not as complete, as it won't work if you have a hash as, e.g., the value of the 'project_name' field, but for a simple 2 level hash, it works great / faster.

Meier
  • 3,858
  • 1
  • 17
  • 46
Kem Mason
  • 1,548
  • 17
  • 26
0

Another alternative is to use the full_dup gem (full disclosure: I am the author of that gem) that handles arrays, hashes, structs, and is extendable to user defined classes.

To use:

require 'full_dup'
# Other code omitted ...
d = h.full_dup

Also note that full_dup handles complex data relationships including those with loops or recursion.

Peter Camilleri
  • 1,882
  • 15
  • 17