DRY策略,用于在未知级别的嵌套对象上循环



我的场景基于Gmail API。

我了解到,电子邮件的消息部分可以根据不同的因素进行深度或浅层嵌套,但主要是附件的存在。

我使用的是Google API Ruby Client gem,所以我不使用JSON,我得到的对象具有所有相同的信息,但我认为JSON表示使我更容易理解我的问题。

一个简单的消息JSON响应如下(一个parts数组,里面有两个散列(:

{
"id": "175b418b1ff69896",
"snippet": "COVID-19: Resources to help your business manage through uncertainty 20 Liters 500 PEOPLE FOUND YOU ON GOOGLE Here are the top search queries used to find you: 20 liters used by 146 people volunteer",
"payload": {
"parts": [
{
"mimeType": "text/plain",
"body": {
"data": "Hey, you found the body of the email! I want this!"
}
},
{
"mimeType": "text/html",
"body": {
"data": "<div>I actually don't want this</div>"
}
}
]
}
}

我想要的价值并不难获得:

response.payload.parts.each do |part|
@body_data = part.body.data if part.mime_type == 'text/plain'
end

但是,带有附件的更复杂电子邮件的JSON响应看起来是这样的(现在parts嵌套了3层(:

{
"id": "175aee26de8209d2",
"snippet": "snippet text...",
"payload": {
"parts": [
{
"mimeType": "multipart/related",
"parts": [
{
"mimeType": "multipart/alternative",
"parts": [
{
"mimeType": "text/plain",
"body": {
"data": "hey, you found me! This is what I want!!"
}
},
{
"mimeType": "text/html",
"body": {
"data": "<div>I actually don't want this one.</div>"
}
}
]
},
{
"mimeType": "image/jpeg"
},
{
"mimeType": "image/png"
},
{
"mimeType": "image/png"
},
{
"mimeType": "image/jpeg"
},
{
"mimeType": "image/png"
},
{
"mimeType": "image/png"
}
]
},
{
"mimeType": "application/pdf"
}
]
}
}

查看其他一些消息,对象可以在parts的1到5个级别(可能更多(之间变化

我需要在未知数量的部分上循环,然后在未知数量嵌套的部分上重复,直到我到达底部,希望找到我想要的东西。

这是我最好的尝试:

def trim_response(response)
# remove headers I don't care about
response.payload.headers.keep_if { |header| @valuable_headers.include? header.name }
# remove parts I don't care about
response.payload.parts.each do |part|
# parts can be nested within parts, within parts, within...
if part.mime_type == @valuable_mime_part && part.body.present?
@body_data = part.body.data
break
elsif part.parts.present?
# there are more layers down
find_body(part)
end
end
end
def find_body(part)
part.parts.each do |sub_part|
if sub_part.mime_type == @valuable_mime_part && sub_part.body.present?
@body_data = sub_part.body.data
break
elsif sub_part.parts.present?
# there are more layers down
######### THIS FEELS BAD!!! ###########
find_body(sub_part)
end
end
end

是的,有一个方法自称。我知道,这就是我来这里的原因。

这确实有效,我已经在几十条消息上测试过了,但是。。。必须有更好、更干燥的方法来做到这一点。

当我不知道嵌套有多深时,我如何递归地循环,然后向下移动一个级别,然后以DRY的方式再次循环

不需要经历所有这些痛苦。只要继续在parts字典中搜索,直到找到第一个不再有parts的值。此时,您的parts变量中有最后一个parts

代码:

reponse = {"id" => "175aee26de8209d2","snippet" => "snippet text...","payload" => {"parts" => [{"mimeType" => "multipart/related","parts" => [{"mimeType" => "multipart/alternative","parts" => [{"mimeType" => "text/plain","body" => {"data" => "hey, you found me! This is what I want!!"}},{"mimeType" => "text/html","body" => {"data" => "<div>I actually don't want this one.</div>"}}]},{"mimeType" => "image/jpeg"}]},{"mimeType" => "application/pdf"}]}}
parts   = reponse["payload"]
parts   = (parts["parts"].send("first") || parts["parts"]) while parts["parts"]
data    = parts["body"]["data"]
puts data

输出:

hey, you found me! This is what I want!!

您可以使用递归计算所需的结果。

def find_it(h, top_key, k1, k2, k3)
return nil unless h.key?(top_key)
recurse(h[top_key], k1, k2, k3)
end
def recurse(h, k1, k2, k3)
return nil unless h.key?(k1)      
h[k1].each do |g|
v = g.dig(k2,k3) || recurse(g, k1 , k2, k3)
return v unless v.nil?
end
nil
end

请参阅哈希#dig。

h1h2等于示例1中给出的两个散列。然后:

find_it(h1, :payload, :parts, :body, :data)
#=> "Hey, you found the body of the email! I want this!"
find_it(h2, :payload, :parts, :body, :data)
#=> "hey, you found me! This is what I want!!"

1.散列h[:payload][:parts].last #=> { "mimeType": "application/pdf" }似乎包含导致问题的隐藏字符。因此,我从h2中删除了那个散列

最新更新