【问题标题】:Ruby: Converting a nested Ruby hash to an un-nested oneRuby:将嵌套的 Ruby 哈希转换为未嵌套的哈希
【发布时间】:2012-08-21 23:52:35
【问题描述】:

现在,我有一个服务器调用回退了以下 Ruby 哈希:

{
  "id"=>"-ct",
  "factualId"=>"",
  "outOfBusiness"=>false,
  "publishedAt"=>"2012-03-09 11:02:01",
  "general"=>{
    "name"=>"A Cote",
    "timeZone"=>"EST",
    "desc"=>"À Côté is a small-plates restaurant in Oakland's charming
            Rockridge district. Cozy tables surround large communal tables in both
            the main dining room and on the sunny patio to create a festive atmosphere.
              Small plates reflecting the best of seasonal Mediterranean cuisine are served
            family-style by a friendly and knowledgeable staff.\nMenu items are paired with
            a carefully chosen selection of over 40 wines by the glass as well as a highly
            diverse bottled wine menu. Specialty drinks featuring fresh fruits, rare
            botaniques and fine liqueurs are featured at the bar.",
    "website"=>"http://acoterestaurant.com/"
  },
  "location"=>{
    "address1"=>"5478 College Ave",
    "address2"=>"",
    "city"=>"Oakland",
    "region"=>"CA",
    "country"=>"US",
    "postcode"=>"94618",
    "longitude"=>37.84235,
    "latitude"=>-122.25222
  },
  "phones"=>{
    "main"=>"510-655-6469",
    "fax"=>nil
  },
  "hours"=>{
    "mon"=>{"start"=>"", "end"=>""},
    "tue"=>{"start"=>"", "end"=>""},
    "wed"=>{"start"=>"", "end"=>""},
    "thu"=>{"start"=>"", "end"=>""},
    "fri"=>{"start"=>"", "end"=>""},
    "sat"=>{"start"=>"", "end"=>""},
    "sun"=>{"start"=>"", "end"=>""},
    "holidaySchedule"=>""
  },
  "businessType"=>"Restaurant"
}

它有几个嵌套的属性,例如:

"wed"=>{"start"=>"", "end"=>""}

我需要将此对象转换为 Ruby 中的非嵌套哈希。理想情况下,我想检测一个属性是否嵌套,并做出相应的响应,即当它确定属性“wed”是嵌套的时,它会提取其数据并存储在字段“wed-start”和“wed-end”或类似的字段中。

有人对如何开始有任何提示吗?

【问题讨论】:

  • 对您在这里使用“JSON”有点困惑。您显示的是 ruby​​ 哈希,而不是 JSON 对象。您是否需要展平该哈希,然后转换回 JSON?或者,您是否只需要获取该红宝石哈希并将其展平?
  • 是的,原来它实际上是一个红宝石对象。我如何将其展平?
  • 有一个名为 flatten 的哈希方法,但我认为这不是您要寻找的。它将散列扁平化为数组。我很好奇你的用例是什么。恕我直言,保持嵌套通常更有意义。动态检索像 hash[var][var2] 这样的值要比像 hash[var + "-" +var2] 这样动态地连接键要容易得多。它的可读性较差,并没有真正获得太多。

标签: ruby hash nested-attributes


【解决方案1】:

编辑:sparsify gem 已作为此问题的通用解决方案发布。


这是我几个月前完成的一个实现。您需要将 JSON 解析为哈希,然后使用 Sparsify 对哈希进行稀疏。

# Extend into a hash to provide sparse and unsparse methods. 
# 
# {'foo'=>{'bar'=>'bingo'}}.sparse #=> {'foo.bar'=>'bingo'}
# {'foo.bar'=>'bingo'}.unsparse => {'foo'=>{'bar'=>'bingo'}}
# 
module Sparsify
  def sparse(options={})
    self.map do |k,v|
      prefix = (options.fetch(:prefix,[])+[k])
      next Sparsify::sparse( v, options.merge(:prefix => prefix ) ) if v.is_a? Hash
      { prefix.join(options.fetch( :separator, '.') ) => v}
    end.reduce(:merge) || Hash.new
  end
  def sparse!
    self.replace(sparse)
  end

  def unsparse(options={})
    ret = Hash.new
    sparse.each do |k,v|
      current = ret
      key = k.to_s.split( options.fetch( :separator, '.') )
      current = (current[key.shift] ||= Hash.new) until (key.size<=1)
      current[key.first] = v
    end
    return ret
  end
  def unsparse!(options={})
    self.replace(unsparse)
  end

  def self.sparse(hsh,options={})
    hsh.dup.extend(self).sparse(options)
  end

  def self.unsparse(hsh,options={})
    hsh.dup.extend(self).unsparse(options)
  end

  def self.extended(base)
    raise ArgumentError, "<#{base.inspect}> must be a Hash" unless base.is_a? Hash
  end
end

用法:

external_data = JSON.decode( external_json )
flattened = Sparsify.sparse( external_data, :separator => '-' )

这最初是因为我们在 Mongo 中存储一组东西而创建的,这允许我们在更新时使用稀疏键(点分隔)来更新嵌套哈希的某些内容,而不会覆盖不相关的键。

【讨论】:

  • 如果有兴趣,我会很乐意 gemify 和维护,以及测试。
【解决方案2】:

这是完整解决方案的第一个步骤。我相信你可以写得更优雅,但这似乎很清楚。如果你把它保存在一个 Ruby 文件中并运行它,你会得到我在下面显示的输出。

class Hash
  def unnest
    new_hash = {}
    each do |key,val|
      if val.is_a?(Hash)
        new_hash.merge!(val.prefix_keys("#{key}-"))
      else
        new_hash[key] = val
      end
    end
    new_hash
  end

  def prefix_keys(prefix)
    Hash[map{|key,val| [prefix + key, val]}].unnest
  end
end

p ({"a" => 2, "f" => 5}).unnest
p ({"a" => {"b" => 3}, "f" => 5}).unnest
p ({"a" => {"b" => {"c" => 4}, "f" => 5}}).unnest

输出:

{"a"=>2, "f"=>5}
{"a-b"=>3, "f"=>5}
{"a-b-c"=>4, "a-f"=>5}

【讨论】:

  • 我可能错了,但这对我来说是一种反模式。尝试获得 p[a-b-c] 比尝试获得 p[a][b][c] 感觉更恶心。当您在哈希中获得更多值时,它会变得混乱。
  • 是的。我没有明确评论这样的解决方案的适当性,只是为人们提供代码:)
  • 谢谢!我会试一试!
  • @Joeyjoejoejr 一个可能使用的示例是,如果您将嵌套哈希填充到无法处理嵌套的 api 或数据存储中。使用起来并不容易,但有时是您唯一的选择。
【解决方案3】:

另一种选择:

class Hash
  def smash(prefix = nil)
    inject({}) do |acc, (k, v)|
      key = prefix.to_s + k
      if Hash === v
        acc.merge(v.smash(key + '-'))
      else
        acc.merge(key => v)
      end
    end
  end
end

hash = {
  'f' => 100,
  'z' => {'j' => 25},
  'a' => {'b' => {'c' => 1}}
}

puts hash.smash # => {"f"=>100, "z-j"=>25, "a-b-c"=>1}

【讨论】:

    【解决方案4】:

    解决此问题的另一种方法不是展平散列,而是像展平一样访问它。例如,给定这个哈希:

    h = {
      'a' => 1,
      'b' => {
        'c' => 2,
        'd' => 3,
      },
    }
    

    那么这个函数:

    NESTED_KEY_SEPARATOR = '-'
    NESTED_KEY_REGEX = /^(.*?)(?:#{NESTED_KEY_SEPARATOR}(.*))?$/
    
    def nested_fetch(key, hash)
      return hash if key.empty?
      first_part_of_key, rest_of_key = NESTED_KEY_REGEX.match(key).captures
      value = hash[first_part_of_key]
      if value.is_a?(Hash)
        nested_hash_fetch(value, rest_of_key || '')
      elsif rest_of_key
        nil
      else
        value
      end
    end
    

    将允许您通过将各个哈希键与 KEY_SEPARATOR 连接在一起来检索嵌套的哈希元素(在此处设置为破折号,但可以是任何在您需要搜索的哈希中从未作为键出现的字符):

    p nested_fetch('a', h)      # => 1
    p nested_fetch('b-c', h)    # => 2
    

    如果你给出一个部分限定的键,你会得到那个点匹配的哈希:

    p nested_fetch('b', h)      # => {"c"=>2, "d"=>3}
    

    如果你给出一个不存在的密钥,你会得到 nil:

    p nested_fetch('b-x', h)    # => nil
    

    如果需要,可以通过简单地将上述代码包含在类 Hash 中,并将 self 作为参数散列的默认值,将其修补到 Hash 上:

    class Hash
      NESTED_KEY_SEPARATOR = '-'
      NESTED_KEY_REGEX = /^(.*?)(?:#{KEY_SEPARATOR}(.*))?$/
    
      def nested_fetch(key, hash = self)
      ...
    end
    

    【讨论】:

      猜你喜欢
      • 2013-11-13
      • 1970-01-01
      • 1970-01-01
      • 2015-08-12
      • 1970-01-01
      • 1970-01-01
      • 2016-09-14
      • 2016-10-31
      • 2020-12-22
      相关资源
      最近更新 更多