【问题标题】:convert hash to object将哈希转换为对象
【发布时间】:2015-01-04 18:03:53
【问题描述】:

我正在尝试将散列和嵌套散列转换为对象。

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

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=&gt;"sdvs"}这部分。我还想将其转换为对象。如果可以,可以吗?

【问题讨论】:

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

标签: ruby-on-rails ruby hash


【解决方案1】:

您可以使用 OpenStruct http://ruby-doc.org/stdlib-2.0.0/libdoc/ostruct/rdoc/OpenStruct.html

user = OpenStruct.new({name: "Jimmy Cool", age: "25"})
user.name #Jimmy Cool
user.age #25

【讨论】:

  • 这是最好的答案!
  • 这种方式不是递归的。不能object.a.b.c,只能object.a
【解决方案2】:

另一种方法是使用 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({... 没有那么深,作为这个完美的例子。
  • 也会将你的值强制转换为字符串或整数:(
【解决方案3】:

你需要添加递归:

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">>

【讨论】:

  • @mohameddiaa27 您的回答也很有帮助,但 openstruct 很好,但不同。
  • h.instance_variables =&gt; [:@a, :@b]。我们不想要=&gt; [:@a, :@b, :@c]吗?
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    您可以在初始化对象时检查v 上的类型,并在为另一个哈希时调用new 以获取新的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
    

    从之前得到的 sn-p 将是:

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

    【讨论】:

    • 很好,Ben,尽管我认为 OP 定义单独的读写访问器的方式过于复杂,并且不如 self.class.send(:attr_accessor, k) 那样读取。
    【解决方案6】:

    如果我正确理解了这个问题,应该这样做:

    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"
    

    【讨论】:

      猜你喜欢
      • 2011-06-29
      • 1970-01-01
      • 2017-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-17
      • 2020-09-15
      相关资源
      最近更新 更多