如何以编程方式识别 JSON 文档中哪些键具有子键值对?

how can I programmatically identify which keys have sub key-value-pairs in a JSON doc?

我获取了一个 JSON 文档,需要以编程方式 "flatten" 另一个第三方服务的密钥。

这意味着,如果我的 JSON 文档返回以下内容:

{'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}

我需要知道如何为这样的第三方服务创建 "flat" 键值对:

first_name = "Joe"
hoffman.patterns = "negativity, self-sabotage"
hoffman.right_road = "happy family"
mbti = "INTJ"

一旦我知道有一个子文档,解析我想我已经想通了只是用 key + '.' + "{subkey}" 附加子键但是现在,不知道哪些是直接键值和哪个有子文件。

问题:

a) 如何解析 JSON 以了解哪些键具有子文档(附加键值)?

b) 关于从数组创建字符串的方法的建议

你也可以像猴子补丁 Hash 那样自己做这个:

class Hash
  def flatten_keys(prefix=nil)
    each_pair.map do |k,v|
      key = [prefix,k].compact.join(".")
      v.is_a?(Hash) ? v.flatten_keys(key) : [key,v.is_a?(Array) ? v.join(", ") : v]
    end.flatten.each_slice(2).to_a
  end
  def to_flat_hash
    Hash[flatten_keys]
  end
end

那就是

require 'json'
h = JSON.parse(YOUR_JSON_RESPONSE)
#=> {'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
h.to_flat_hash
#=> {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}

也可以使用额外的嵌套

h =  {"first_name"=>"Joe", "hoffman"=>{"patterns"=>["negativity", "self-sabotage"], "right_road"=>"happy family", "wrong_road"=>{"bad_choices"=>["alcohol", "heroin"]}}, "mbti"=>"INTJ"}
h.to_flat_hash
#=> {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "hoffman.wrong_road.bad_choices"=>"alcohol, heroin", "mbti"=>"INTJ"}

快速而肮脏的递归过程:

# assuming you've already `JSON.parse` the incoming json into this hash:
a = {'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}

# define a recursive proc:
flatten_keys = -> (h, prefix = "") do
  @flattened_keys ||= {}
  h.each do |key, value|
    # Here we check if there's "sub documents" by asking if the value is a Hash
    # we also pass in the name of the current prefix and key and append a . to it
    if value.is_a? Hash
      flatten_keys.call value, "#{prefix}#{key}."
    else
      # if not we concatenate the key and the prefix and add it to the @flattened_keys hash 
      @flattened_keys["#{prefix}#{key}"] = value
    end
  end
  @flattened_keys
end

flattened = flatten_keys.call a
# => "first_name"=>"Joe", "hoffman.patterns"=>["negativity", "self-sabotage"], "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}

然后,要将数组转换为字符串,只需 join 它们:

flattened.inject({}) do |hash, (key, value)|
  value = value.join(', ') if value.is_a? Array
  hash.merge! key => value
end

# => {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}

另一种方式,灵感来自this post

def flat_hash(h,f=[],g={})
  return g.update({ f=>h }) unless h.is_a? Hash
  h.each { |k,r| flat_hash(r,f+[k],g) }
  g
end

h = { :a => { :b => { :c => 1,
                      :d => 2 },
              :e => 3 },
      :f => 4 }

result = {}
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
  .each{ |k, v| result[k.join('.')] = v } #=> {"a.b.c"=>1, "a.b.d"=>2, "a.e"=>3, "f"=>4}