【问题标题】:Safely assign value to nested hash using Hash#dig or Lonely operator(&.)使用 Hash#dig 或 Lonely operator(&.) 为嵌套哈希安全地赋值
【发布时间】:2016-04-09 19:06:40
【问题描述】:
h = {
  data: {
    user: {
      value: "John Doe" 
    }
  }
}

要给嵌套散列赋值,我们可以使用

h[:data][:user][:value] = "Bob"

但是如果中间缺少任何部分,就会导致错误。

有点像

h.dig(:data, :user, :value) = "Bob"

不起作用,因为还没有可用的Hash#dig=

为了安全地赋值,我们可以这样做

h.dig(:data, :user)&.[]=(:value, "Bob")    # or equivalently
h.dig(:data, :user)&.store(:value, "Bob")

但是有更好的方法吗?

【问题讨论】:

  • 对于它的价值,这里已经讨论过(并被 Matz 拒绝(暂时):bugs.ruby-lang.org/issues/11747
  • @JordanRunning 但它可以在 Ruby 2.5 中运行!
  • @MikeSzyndel 具体来说,在 Ruby 2.5 中起作用的是什么?仍然没有Hash/Array#bury 或等效方法。
  • 我刚刚成功使用h.dig(:data, :user)&.store(:value, "Bob") 编辑了一个复杂的哈希(解析的JSON文件)。没有埋葬,但这种方法很干净,对我来说足够有用:)

标签: ruby hash dig ruby-2.3 safe-navigation-operator


【解决方案1】:

这并非没有注意事项(如果您从其他地方接收哈希,则不起作用),但一个常见的解决方案是:

hash = Hash.new {|h,k| h[k] = h.class.new(&h.default_proc) }

hash[:data][:user][:value] = "Bob"
p hash
# => { :data => { :user => { :value => "Bob" } } }

【讨论】:

  • 不幸的是,哈希来自其他地方。但是感谢您的 sn-p!
  • 您还可以为现有哈希设置默认 proc:hsh.default_proc = proc { |h,k| h[k] = Hash.new(&h.default_proc) }
【解决方案2】:

以@rellampec 的回答为基础,不会引发错误:

def dig_set(obj, keys, value)
  key = keys.first
  if keys.length == 1
    obj[key] = value
  else
    obj[key] = {} unless obj[key]
    dig_set(obj[key], keys.slice(1..-1), value)
  end
end

obj = {d: 'hey'}
dig_set(obj, [:a, :b, :c], 'val')
obj #=> {d: 'hey', a: {b: {c: 'val'}}} 

【讨论】:

    【解决方案3】:

    有趣的一个:

    def dig_set(obj, keys, value)
      if keys.length == 1
        obj[keys.first] = value
      else
        dig_set(obj[keys.first], keys.slice(1..-1), value)
      end
    end
    

    如果没有 [][]= 方法,无论如何都会引发异常。

    【讨论】:

      【解决方案4】:

      我找到了一个简单的解决方案来设置嵌套散列的值,即使缺少父键,即使散列已经存在。给定:

      x = { gojira: { guitar: { joe: 'charvel' } } }
      

      假设您想包含 mario 的鼓以导致:

      x = { gojira: { guitar: { joe: 'charvel' }, drum: { mario: 'tama' } } }
      

      我最终用猴子修补哈希:

      class Hash
      
          # ensures nested hash from keys, and sets final key to value
          # keys: Array of Symbol|String
          # value: any
          def nested_set(keys, value)
            raise "DEBUG: nested_set keys must be an Array" unless keys.is_a?(Array)
      
            final_key = keys.pop
            return unless valid_key?(final_key)
            position = self
            for key in keys
              return unless valid_key?(key)
              position[key] = {} unless position[key].is_a?(Hash)
              position = position[key]
            end
            position[final_key] = value
          end
      
          private
      
            # returns true if key is valid
            def valid_key?(key)
              return true if key.is_a?(Symbol) || key.is_a?(String)
              raise "DEBUG: nested_set invalid key: #{key} (#{key.class})"
            end
      end
      

      用法:

      x.nested_set([:instrument, :drum, :mario], 'tama')
      

      你的例子的用法:

      h.nested_set([:data, :user, :value], 'Bob')
      

      我错过了什么警告?在不牺牲可读性的情况下编写代码有更好的方法吗?

      【讨论】:

        【解决方案5】:

        在寻找类似问题的答案时,我偶然发现了一个类似于@niels-kristian 答案的界面,但还想支持命名空间定义参数,例如 xpath。

        def deep_merge(memo, source)
          # From: http://www.ruby-forum.com/topic/142809
          # Author: Stefan Rusterholz
          merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
          memo.merge!(source, &merger)
        end
        
        # Like Hash#dig, but for setting a value at an xpath
        def bury(memo, xpath, value, delimiter=%r{\.})
          xpath = xpath.split(delimiter) if xpath.respond_to?(:split)
          xpath.map!{|x|x.to_s.to_sym}.push(value)
          deep_merge(memo, xpath.reverse.inject { |memo, field| {field.to_sym => memo} })
        end
        

        嵌套哈希有点像 xpath,dig 的反义词是 bury

        irb(main):014:0> memo = {:test=>"value"}
        => {:test=>"value"}
        irb(main):015:0> bury(memo, 'test.this.long.path', 'value')
        => {:test=>{:this=>{:long=>{:path=>"value"}}}}
        irb(main):016:0> bury(memo, [:test, 'this', 2, 4.0], 'value')
        => {:test=>{:this=>{:long=>{:path=>"value"}, :"2"=>{:"4.0"=>"value"}}}}
        irb(main):017:0> bury(memo, 'test.this.long.path.even.longer', 'value')
        => {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}}, :"2"=>{:"4.0"=>"value"}}}}
        irb(main):018:0> bury(memo, 'test.this.long.other.even.longer', 'other')
        => {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}, :other=>{:even=>{:longer=>"other"}}}, :"2"=>{:"4.0"=>"value"}}}}
        

        【讨论】:

          猜你喜欢
          • 2016-04-09
          • 1970-01-01
          • 2019-10-31
          • 1970-01-01
          • 2020-07-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-22
          相关资源
          最近更新 更多