I’ve got three.
ATTEMPT #1
My first attempt was pretty simple:
require 'ostruct'
def hashes_to_openstructs( obj )
return obj unless Hash === obj
OpenStruct.new( Hash[
*obj.inject( [] ) { |a, (k, v)| a.push k,
hashes_to_openstructs( v ) }
] )
end
The main idea here was to build the OpenStruct all at once, rather than
resorting to a bunch of Object#send( “#{name}=”, value ) calls. To do
this, however, we end up going from a hash, to a sequence of pairs, to a
flat array of alternating keys and values, back to a hash, and then
finally to an OpenStruct.
ATTEMPT #2:
Then I got bored and decided I’d deal with the case of a self-recursive
hash. My first attempt used lazy.rb
( http://moonbase.rydia.net/software/lazy.rb ); I simply made the above
hashes_to_openstructs() function lazy (by wrapping its innards in
promise {}), then memoized it using a hash:
require 'ostruct'
require 'lazy'
def hashes_to_openstructs( obj, memo={} )
return obj unless Hash === obj
memo[obj.object_id] ||= promise {
OpenStruct.new( Hash[
*obj.inject( [] ) { |a, (k, v)|
a.push k, hashes_to_openstructs( v, memo )
}
] )
}
end
ATTEMPT #3:
While that’s sort of clever, I couldn’t help but think there was a
simpler solution. It involved building the OpenStruct incrementally,
using the same Object#send calls I’d been hoping to avoid.
def hashes_to_openstructs( obj, memo={} )
return obj unless Hash === obj
os = memo[obj] = OpenStruct.new
obj.each do |k, v|
os.send( "#{k}=", memo[v] || hashes_to_openstructs( v, memo
) )
end
os
end
Once again, however, memoization wins.
-mental