【问题标题】:Convert a hash into a struct将哈希转换为结构
【发布时间】:2012-08-14 23:16:44
【问题描述】:

如何在 ruby​​ 中将哈希转换为结构?

鉴于此:

h = { :a => 1, :b => 2 }

我想要一个这样的结构:

s.a == 1
s.b == 2

【问题讨论】:

    标签: ruby


    【解决方案1】:

    这基于上面@elado 的回答,但使用keyword_init 值(Struct Documentation

    你可以这样做:

    Person = Struct.new(:first_name, :last_name, :age, keyword_init: true)
    
    person_hash = { first_name: "Foo", last_name: "Bar", age: 29 }
    
    person = Person.new(person_hash)
    
    => #<struct Person first_name="Foo", last_name="Bar", age=29>
    

    【讨论】:

    • 请注意,keyword_init: true 仅在 Ruby 2.5 中引入,而 2.4 仍在支持范围内。
    【解决方案2】:

    以下内容以可靠的方式从哈希创建结构体(因为 ruby​​ 不保证哈希顺序):

    s = Struct.new(*(k = h.keys)).new(*h.values_at(*k))
    

    【讨论】:

      【解决方案3】:

      如果不是特别必须是Struct,而是可以是OpenStruct

      pry(main)> require 'ostruct'
      pry(main)> s = OpenStruct.new(h)
      => #<OpenStruct a=1, b=2>
      pry(main)> puts s.a, s.b
      1
      2
      

      【讨论】:

      • 请注意,OpenStructs 使用起来可能非常缓慢。适用于少量小物体,但它们的缩放比例很差。更多信息:stackoverflow.com/questions/1177594/ruby-struct-vs-openstruct
      • @AFaderDarkly 我认为他们的速度问题有据可查,但谢谢。
      • 我相信最后一条命令应该是:pry(main)&gt; puts s.a, s.b 或者第 2 行应该是 pry(main)&gt; o = OpenStruct.new(h)
      • @DaveNewton:在这个页面上他们没有。或者在许多推荐使用它们的网站上。最初的问题要求一个结构 - 我认为这只是礼貌 - 警告使用不同解决方案所固有的权衡。
      • fwiw 由于性能问题,我来到这个页面希望用 Struct 替换 OpenStruct。请至少更新您的答案并发出警告。
      【解决方案4】:

      如果您需要递归版本,这里有一个简洁的 hack/解决方案

      a_hash = {a: {b: {c: 'x'}}}
      structs_inside_structs = JSON.parse(
        a_hash.to_json, object_class: OpenStruct
      )
      # => #<OpenStruct a=#<OpenStruct b=#<OpenStruct c="x">>>
      structs_inside_structs.a.b.c
      # => "x"
      

      【讨论】:

        【解决方案5】:

        由于 Ruby 1.9+ 中保证了哈希键顺序:

        Struct.new(*h.keys).new(*h.values)
        

        【讨论】:

        • 很高兴知道。我虽然在某个地方读到过,但不记得在哪里。谢谢!
        • 这似乎不起作用(至少在 Ruby 2.2.0 中):Struct.new(*h.keys) raises: NameError: identifier my_key needs to be constant
        • @Joe 它工作正常。我认为您为哈希使用了字符串键,这是导致错误的原因。该错误告诉您它需要一个常量值,即符号而不是字符串。我可以重现 2.1.5 中的错误,如果我切换到符号就会消失。
        • @Joe 次要更正:NameError 的真正原因是因为如果您提供一个字符串作为 Struct::new 的第一个参数,它假定它是它将创建的类的名称,所以它将尝试将其转换为常量并在字符串全部为小写时失败(因为常量在 Ruby 中必须大写)。解决方法是 a) 提供大写字符串作为第一个参数 (Struct.new('MyClass', *h.keys)) 或按照 ehsanul 的建议使用符号作为哈希键。
        • 遇到了和 Joe 一样的问题,必须先转换我的字符串参数:Struct.new(*h.keys.map(&amp;:to_sym)).new(*h.values)
        【解决方案6】:

        如果你已经定义了一个结构,并且你想用哈希实例化一个实例:

        Person = Struct.new(:first_name, :last_name, :age)
        
        person_hash = { first_name: "Foo", last_name: "Bar", age: 29 }
        
        person = Person.new(*person_hash.values_at(*Person.members))
        
        => #<struct Person first_name="Foo", last_name="Bar", age=29>
        

        【讨论】:

        • 谢谢!我正在设计一个可以从命令或外部代码调用的 gem,每个都提供选项(分别使用 OptionParser 或 Hash)。这允许在我的 gem 初始化期间轻松过滤选项。并且 Struct 还有助于“自我记录”允许的选项!
        • 非常好的解决方案
        【解决方案7】:

        拥有Hash#to_struct 非常实用:

        class Hash
          def to_struct
            Struct.new(*keys).new(*values)
          end
        end
        

        还有一些例子:

        >> { a: 1, b: 2 }.to_struct
        => #<struct a=1, b=2>
        >> { a: 1, b: 2 }.to_struct.a
        => 1
        >> { a: 1, b: 2 }.to_struct.b
        => 2
        >> { a: 1, b: 2 }.to_struct.c
        NoMethodError: undefined method `c` for #<struct a=1, b=2>
        

        适用于数组的深层to_struct

        class Array
          def to_struct
            map { |value| value.respond_to?(:to_struct) ? value.to_struct : value }
          end
        end
        
        class Hash
          def to_struct
            Struct.new(*keys).new(*values.to_struct)
          end
        end
        

        【讨论】:

        • 很好,但是如果它的json hash['name'],需要符号化keys。
        【解决方案8】:

        这提供了一个干净的纯只读对象,类似于 ruby​​ 结构,但具有深度转换和额外的to_h 方法,可以在任何时候将结构作为哈希。

        例子

        foo = {a:{b:{c:123}}}.to_struct
        foo.a.b.c # 123
        foo.a.to_h # {b:{c:123}}
        

        Ruby 代码

        class Hash
          def to_struct
            Class.new.tap do |c|
              c.define_singleton_method(:to_h) do
                m_list = methods(false) - [:to_h]
                m_list.inject({}) do |h, m|
                  h[m] = send(m)
                  h[m] = h[m].to_h if h[m].class == Class
                  h
                end
              end
        
              each do |k, v|
                v = v.to_struct if v.class == Hash
                c.define_singleton_method(k) { v }
              end
            end
          end
        end
        

        不完全是问题的答案(不是 ruby​​ Struct 对象),但我在寻找答案时只需要这个,所以我将在此处发布答案。

        【讨论】:

          【解决方案9】:

          这是一个将值映射到 Struct 正确顺序的示例:

          require 'securerandom'
          
          Message = Struct.new(:to, :from, :message, :invitee)
          
          message_params = {from: "my@email.address", to: "your@email.address",
                  invitee: SecureRandom.uuid, message: "hello"}
          
          if Message.members.sort == message_params.keys.sort
            # Do something with the return struct object here
            Message.new *Message.members.map {|k| message_params[k] } 
          else
            raise "Invalid keys for Message"
          end
          

          【讨论】:

            【解决方案10】:

            您可以使用以下代码从 Hash 转换为 Struct:

            Struct.new(*my_hash.keys.map(&:to_sym)).new(*my_hash.values)
            

            确保将所有键转换为符号,因为它会在字符串键上出错,NameError: identifier my_key needs to be constant

            我个人建议在 Hash 类中添加一个猴子补丁,因为这是一个非常强大的操作

            # config/initializers/core_extensions.rb
            
            Hash.class_eval do
              def to_struct
                Struct.new(*keys.map(&:to_sym)).new(*values)
              end
            end
            

            【讨论】:

              【解决方案11】:

              TL;DR;

              使用 OpenStruct.new(hash)

              新的更好的方法

              hash = {"Name" => "Taimoor", "id" => "222", "SomeKey" => "Some value", "attributes" => {"type" => 'User', 'role' => 'manager'}}
              OpenStruct.new(hash)
              

              这只会将第一级散列转换为结构。要将嵌套属性哈希转换为结构,我这样做了

              hash.attributes = OpenStruct.new(hash.attributes)
              

              老路

              我有一个带有字符串键的哈希

              {"Name" => "Taimoor", "id" => "222", "SomeKey" => "Some value"}
              

              所以我需要首先将键转换为符号hash.keys.map(&amp;:to_sym) 并访问原始哈希中的这些键,我在哈希上使用了hash.with_indifferent_access 方法。

              def hash_to_struct(hash)
                  Struct.new(*(k = hash.keys.map(&:to_sym)))
                      .new(*hash.with_indifferent_access.values_at(*k))
              end
              

              现在它适用于哈希的符号和字符串类型键。

              注意:这只会将哈希转换为一级结构。对于嵌套散列,需要在每一层嵌套上调用该方法。

              【讨论】:

                【解决方案12】:
                require 'ds_hash'
                
                data = {a: {b: 123 }}.to_struct
                
                data.a.b == 123       # true
                data.a   == {b: 123 } # true
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2019-10-27
                  • 2019-05-23
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-10-29
                  • 2012-11-11
                  • 1970-01-01
                  相关资源
                  最近更新 更多