Safely assign value to nested hash using Hash#dig or Lonely operator(&.)

后端 未结 4 944
执笔经年
执笔经年 2020-12-30 01:55
h = {
  data: {
    user: {
      value: \"John Doe\" 
    }
  }
}

To assign value to the nested hash, we can use

h[:data][:user][:         


        
相关标签:
4条回答
  • 2020-12-30 02:34

    I found a simple solution to set the value of a nested hash, even if a parent key is missing, even if the hash already exists. Given:

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

    Suppose you wanted to include mario's drum to result in:

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

    I ended up monkey-patching Hash:

    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
    

    usage:

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

    usage for your example:

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

    any caveats i missed? any better way to write the code without sacrificing readability?

    0 讨论(0)
  • 2020-12-30 02:38

    And building on @rellampec's answer, ones that does not throw errors:

    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'}}} 
    
    0 讨论(0)
  • 2020-12-30 02:38

    interesting one:

    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
    

    will raise an exception anyways if there's no [] or []= methods.

    0 讨论(0)
  • 2020-12-30 02:49

    It's not without its caveats (and doesn't work if you're receiving the hash from elsewhere), but a common solution is this:

    hash = Hash.new {|h,k| h[k] = h.class.new(&h.default_proc) }
    
    hash[:data][:user][:value] = "Bob"
    p hash
    # => { :data => { :user => { :value => "Bob" } } }
    
    0 讨论(0)
提交回复
热议问题