如何安全地解析Ruby Hash中的数据?

时间:2016-08-14 18:33:32

标签: ruby-on-rails json ruby parsing hash

我正在将JSON结果解析为Ruby哈希。 JSON结果如下所示:

{
  "records": [
    {
      "recordName": "7DBC4FAD-D18C-476A-89FB-14A515098F34",
      "recordType": "Media",
      "fields": {
        "data": {
          "value": {
            "fileChecksum": "ABCDEFGHIJ",
            "size": 9633842,
            "downloadURL": "https://cvws.icloud-content.com/B/ABCDEF"
          },
          "type": "ASSETID"
        }
      },
      "recordChangeTag": "ii23box2",
      "created": {
        "timestamp": 1449863552482,
        "userRecordName": "_abcdef",
        "deviceID": "12345"
      },
      "modified": {
        "timestamp": 1449863552482,
        "userRecordName": "_abcdef",
        "deviceID": "12345"
      }
    }
  ]
}

我不能保证它会返回任何/所有这些值,或者每个值都是某种类型(例如数组,哈希,字符串,数字),如果我不正确地调用它,那么我得到崩溃。

现在我需要对'records'数组中的第一项使用downloadURL,或者像使用Swift库SwiftyJSON(我更熟悉)那样编写它:

json["records"][0]["fields"]["data"]["value"]["downloadURL"]

我想知道在Ruby中安全地执行此操作的最安全/最佳/标准方法是什么。也许我在想错了?

3 个答案:

答案 0 :(得分:3)

在ruby 2.3及以上版本中,您可以使用Hash#digArray#dig

json = JSON.parse(...)
json.dig('records', 0, 'fields', 'data', 'value', 'downloadURL')

如果任何中间值为零,您将获得nil。如果其中一个中间值没有dig方法,例如,如果`json ['记录'] [0] ['字段']意外地这将引发TypeError。

答案 1 :(得分:1)

从文档(http://ruby-doc.org/stdlib-2.2.3/libdoc/json/rdoc/JSON.html):

require 'json'

my_hash = JSON.parse('{"hello": "goodbye"}')
puts my_hash["hello"] => "goodbye"

如果您担心自己可能没有某些数据。看到这个问题: Equivalent of .try() for a hash to avoid "undefined method" errors on nil?

答案 2 :(得分:1)

您可以使用递归搜索json对象中包含的每个对象 JSON模块的recurse_proc方法。

以下是使用您提供的数据的示例。

require 'json'

json_string = '{
  "records": [
    {
      "recordName": "7DBC4FAD-D18C-476A-89FB-14A515098F34",
      "recordType": "Media",
      "fields": {
        "data": {
          "value": {
            "fileChecksum": "ABCDEFGHIJ",
            "size": 9633842,
            "downloadURL": "https://cvws.icloud-content.com/B/ABCDEF"
          },
          "type": "ASSETID"
        }
      },
      "recordChangeTag": "ii23box2",
      "created": {
        "timestamp": 1449863552482,
        "userRecordName": "_abcdef",
        "deviceID": "12345"
      },
      "modified": {
        "timestamp": 1449863552482,
        "userRecordName": "_abcdef",
        "deviceID": "12345"
      }
    }
  ]
}'

json_obj = JSON.parse(json_string)
JSON.recurse_proc(json_obj) do |obj|
  if obj.is_a?(Hash) && obj['downloadURL']
    puts obj['downloadURL']
  end
end

根据弗雷德里克的回答和卡里的评论更新

我最初假设你只是想在json的某个地方找到downloadURL而不会崩溃,但根据Frederick的回答和Cary的评论,假设你只想要找到downloadURL,如果它位于完全路径,而不是它是否存在。 基于弗雷德里克的回答和Cary的评论,这里有一些其他选项可以安全地找到预期路径下的downloadURL。

path = ['records', 0, 'fields', 'data', 'value', 'downloadURL']
parsed_json_obj = JSON.parse(json_string)
node_value = path.reduce(parsed_json_obj) do |json,node|
  if json.is_a?(Hash) || (json.is_a?(Array) && node.is_a?(Integer))
    path = path.drop 1
    json[node]
  else
    node unless node == path.last 
  end
end

puts node_value || "not_found"

path = ['records', 0, 'fields', 'data', 'value', 'downloadURL']
begin
  node_value = parsed_json_obj.dig(*path)
rescue TypeError
  node_value = "not_found"
end

puts node_value || "not_found"

BTW,这假设json至少是有效的,如果不是给定的,你可能也想将JSON.parse包装在begin-rescue-end块中。