使用Ruby / Rails将嵌套哈希展平为单个哈希

我想在不同深度的哈希中“扁平化”(不是经典意义上的.flatten ),如下所示:

 { :foo => "bar", :hello => { :world => "Hello World", :bro => "What's up dude?", }, :a => { :b => { :c => "d" } } } 

下到一个单一级别的哈希,并且所有嵌套的键合并为一个字符串,因此它将成为:

 { :foo => "bar", :"hello.world" => "Hello World", :"hello.bro" => "What's up dude?", :"abc" => "d" } 

但我想不出一个好办法。 它有点像Rails添加到deep_ helper函数,但不完全相同。 我知道递归将是这里的方式,但我从未在Ruby中编写过递归函数。

你可以这样做:

 def flatten_hash(hash) hash.each_with_object({}) do |(k, v), h| if v.is_a? Hash flatten_hash(v).map do |h_k, h_v| h["#{k}.#{h_k}".to_sym] = h_v end else h[k] = v end end end flatten_hash(:foo => "bar", :hello => { :world => "Hello World", :bro => "What's up dude?", }, :a => { :b => { :c => "d" } }) # => {:foo=>"bar", # => :"hello.world"=>"Hello World", # => :"hello.bro"=>"What's up dude?", # => :"abc"=>"d"} 

因为我很喜欢Enumerable#reduce and hate lines:

 def flatten_hash(param, prefix=nil) param.each_pair.reduce({}) do |a, (k, v)| v.is_a?(Hash) ? a.merge(flatten_hash(v, "#{prefix}#{k}.")) : a.merge("#{prefix}#{k}".to_sym => v) end end 

 irb(main):118:0> flatten_hash(hash) => {:foo=>"bar", :"hello.world"=>"Hello World", :"hello.bro"=>"What's up dude?", :"abc"=>"d"} 

这里最高投票的答案不会一直压扁对象,它不会使数组变平。 我已经在下面更正了这个并提供了一个比较:

 x = { x: 0, y: { x: 1 }, z: [ { y: 0, x: 2 }, 4 ] } def top_voter_function ( hash ) hash.each_with_object( {} ) do |( k, v ), h| if v.is_a? Hash top_voter_function( v ).map do |h_k, h_v| h[ "#{k}.#{h_k}".to_sym ] = h_v end else h[k] = v end end end def better_function ( a_el, a_k = nil ) result = {} a_el = a_el.as_json a_el.map do |k, v| k = "#{a_k}.#{k}" if a_k.present? result.merge!( [Hash, Array].include?( v.class ) ? better_function( v, k ) : ( { k => v } ) ) end if a_el.is_a?( Hash ) a_el.uniq.each_with_index do |o, i| i = "#{a_k}.#{i}" if a_k.present? result.merge!( [Hash, Array].include?( o.class ) ? better_function( o, i ) : ( { i => o } ) ) end if a_el.is_a?( Array ) result end top_voter_function( x ) #=> {:x=>0, :"yx"=>1, :z=>[{:y=>0, :x=>2}, 4]} better_function( x ) #=> {"x"=>0, "yx"=>1, "z.0.y"=>0, "z.0.x"=>2, "z.1"=>4} 

我很欣赏这个问题有点旧,我在网上寻找上面代码的比较,这就是我发现的。 当与Mixpanel等分析服务的事件一起使用时,它的效果非常好。

在我的情况下,我正在使用Parameters类,所以上述解决方案都不适合我。 我为解决问题所做的是创建以下function:

 def flatten_params(param, extracted = {}) param.each do |key, value| if value.is_a? ActionController::Parameters flatten_params(value, extracted) else extracted.merge!("#{key}": value) end end extracted end 

然后你可以像flatten_parameters = flatten_params(params)一样使用它。 希望这可以帮助。

或者如果你想要一个猴子修补版本或Uri的答案去你的your_hash.flatten_to_root

 class Hash def flatten_to_root self.each_with_object({}) do |(k, v), h| if v.is_a? Hash v.flatten_to_root.map do |h_k, h_v| h["#{k}.#{h_k}".to_sym] = h_v end else h[k] = v end end end end