[SOLUTION] Hash to OpenStruct (#81)


#1

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


#2

My solution isn’t quite as robust as mental’s (particularly, no
support for self-reference), but here it is:

class Object
def to_openstruct
self
end
end

class Array
def to_openstruct
map{ |el| el.to_openstruct }
end
end

class Hash
def to_openstruct
mapped = {}
each{ |key,value| mapped[key] = value.to_openstruct }
OpenStruct.new(mapped)
end
end

module YAML
def self.load_openstruct(source)
self.load(source).to_openstruct
end
end

Usage is simply:

require ‘to_openstruct’
p YAML.load_openstruct(File.read(“sample.yml”)

Jacob F.