Ruby:将嵌套哈希转换为对象?

28

我想将包含嵌套哈希的哈希转换为对象,以便可以使用点语法访问属性(包括嵌套属性)。

到目前为止,此代码成功地将第一个哈希对象转换:

class Hashit
  def initialize(hash)
    hash.each do |k,v|
      self.instance_variable_set("@#{k}", v)
      self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})
      self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})
    end
  end
end

问题在于,这种方法无法处理嵌套的哈希表:

h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
 => #<Hashit:0x00000006516c78 @a="123r", @b={:c=>"sdvs"}> 

请注意,输出结果中@b={:c=>"sdvs"}没有被转换,它仍然是一个哈希表。

如何将嵌套的哈希表转换为对象?


如果您要求h具有实例变量[:@a, :@b, :@c],正如@Ben和我所假设的那样,您选择的答案是不正确的。 - Cary Swoveland
6个回答

75

3
这种方式不是递归的。你不能够使用 object.a.b.c 的形式,只能使用 object.a 的形式。 - Allen
请注意,您可能需要require 'ostruct'才能使其正常工作。 - PressingOnAlways

56

另一种方法是使用JSON和OpenStruct,它们是标准的Ruby库:

irb:
> require 'JSON'
=> true

> r = JSON.parse({a: { b: { c: 1 }}}.to_json, object_class: OpenStruct)
=> #<OpenStruct a=#<OpenStruct b=#<OpenStruct c=1>>>

> r.a.b.c
=> 1

OpenStruct.new({...不像这个完美的例子那么深。 - Sergio Belevskij
还会将您的值强制转换为字符串或整数 :( - Patrick Dougall
不适用于哈希类(Hash class)响应的键。例如尝试以下内容:r = JSON.parse({a: { b: { display: 1 }}}.to_json,object_class: OpenStruct) - Mark Schneider

8

您需要添加递归:

class Hashit
  def initialize(hash)
    hash.each do |k,v|
      self.instance_variable_set("@#{k}", v.is_a?(Hash) ? Hashit.new(v) : v)
      self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})
      self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})
    end
  end
end

h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
# => #<Hashit:0x007fa6029f4f70 @a="123r", @b=#<Hashit:0x007fa6029f4d18 @c="sdvs">>

1
@mohameddiaa27,你的回答也很有帮助,但OpenStruct是另一种不同的东西。 - hehehuhu
h.instance_variables => [:@a, :@b]。我们不想要 => [:@a, :@b, :@c] 吗? - Cary Swoveland

1

Ruby有一个内置的数据结构OpenStruct,可以解决这类问题。但是,存在一个问题,它不支持递归。因此,您可以像这样扩展OpenStruct类:

# Keep this in lib/open_struct.rb
class OpenStruct
  def initialize(hash = nil)
    @table = {}
    if hash
      hash.each_pair do |k, v|
        k = k.to_sym
        @table[k] = v.is_a?(Hash) ? OpenStruct.new(v) : v
      end
    end
  end

  def method_missing(mid, *args) # :nodoc:
    len = args.length
    if mname = mid[/.*(?==\z)/m]
      if len != 1
        raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
      end
      modifiable?[new_ostruct_member!(mname)] = args[0].is_a?(Hash) ? OpenStruct.new(args[0]) : args[0]
    elsif len == 0 # and /\A[a-z_]\w*\z/ =~ mid #
      if @table.key?(mid)
        new_ostruct_member!(mid) unless frozen?
        @table[mid]
      end
    else
      begin
        super
      rescue NoMethodError => err
        err.backtrace.shift
        raise
      end
    end
  end
end

记得下次想使用OpenStruct时要require 'open_struct.rb'

现在你可以这样做:

person = OpenStruct.new
person.name = "John Smith"
person.age  = 70
person.more_info = {interests: ['singing', 'dancing'], tech_skills: ['Ruby', 'C++']}

puts person.more_info.interests
puts person.more_info.tech_skills

0

在初始化对象并调用new以获取另一个哈希时,您可以检查v上的类型,然后获取新的Hashit

class Hashit
  def initialize(hash)
    hash.each do |k,v|
      self.instance_variable_set("@#{k}", v.is_a?(Hash) ? Hashit.new(v) : v)
      self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})
      self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})
    end
  end
end

之前的代码片段将会是:

h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
=> #<Hashit:0x007fa71421a850 @a="123r", @b=#<Hashit:0x007fa71421a5a8 @c="sdvs">>

不错,Ben。虽然我认为原帖中定义单独的读写访问器的方式过于复杂,而且不如只使用 self.class.send(:attr_accessor, k) 读起来顺畅。 - Cary Swoveland

0
如果我理解问题正确,这应该可以解决:
class Hashit
  def initialize(hash)
    convert_to_obj(hash)
  end

  private

  def convert_to_obj(h)
    h.each do |k,v|
      self.class.send(:attr_accessor, k)
      instance_variable_set("@#{k}", v) 
      convert_to_obj(v) if v.is_a? Hash
    end
  end
end

h = Hashit.new( { a: '123r',
      b: { c: 'sdvs', d: { e: { f: 'cat' }, g: {h: 'dog'} } } })
  #=> #<Hashit:0x000001018eee58 @a="123r",
  #     @b={:c=>"sdvs", :d=>{:e=>{:f=>"cat"}, :g=>{:h=>"dog"}}},
  #       @c="sdvs", @d={:e=>{:f=>"cat"}, :g=>{:h=>"dog"}},
  #       @e={:f=>"cat"}, @f="cat", @g={:h=>"dog"}, @h="dog">
h.instance_variables
  #=> [:@a, :@b, :@c, :@d, :@e, :@f, :@g, :@h]
Hashit.instance_methods(false)
  #=> [:a, :a=, :b, :b=, :c, :c=, :d, :d=, :e, :e=, :f, :f=, :g, :g=, :h, :h=]
h.d
  #=> {:e=>{:f=>"cat"}}
h.d = "cat"
h.d
  #=> "cat"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接