带有URLSession.shared.dataTask错误的JSONSerialization

时间:2018-01-18 14:40:04

标签: json swift url

作为自学Swift的一部分,我正在研究天气应用程序。我目前正在尝试整合天气警报。在序列化API调用返回的数据后,我使用一个名为AlertData的结构来初始化从对weather.gov的API调用返回的数据。或者,至少这是计划。我已经将其我的类建模为从weather.gov请求数据的其他类,但为了获得警报,我需要能够在我的dataTask中发送变量参数。我使用Apple的应用程序开发与Swift的URL扩展(下面的代码),并设置代码以发出用户当前位置的参数,以获取用户当前的警报。

当我尝试在AlertDataController类(下面的代码)中构造对weather.gov的API调用时,我的问题出现了。 Xcode不断抛出不同的错误,我不知道为什么。我想在下面的代码中使用一个保护语句,但是会引发错误"不能强制解包非可选类型的值' [[String:Any]]' "在我的代码中显示。当我在展开后将其作为一个简单的常量赋值时,它也会抛出相同的错误,因为扩展会返回一个可选的URL。

当我直接从guard语句中的字符串构造URL时,相同的代码可以完美地工作,如下所示:

guard let url = URL(string: (baseURL + locationString + stations)) else {

我错过了什么?抛出我的错误的地方是dataTask内部,无论它是如何到达的,变量url都是一个未包装的URL。提前谢谢。

控制器类:

import Foundation
import CoreLocation

struct AlertDataController {

    func checkWxAlert(location: CLLocation, completion: @escaping (AlertData?) -> Void) {
        let baseURL = URL(string: "https://api.weather.gov/alert")!
        let locationString = "\(location.coordinate.latitude),\(location.coordinate.longitude)"
        var query = [
            "active": "1",
            "point": locationString
        ]

       guard let url = baseURL.withQueries(query) else {

            completion(nil)
            print("Unable to build URL in AlertDataController.checkWxAlert with supplied queries.")
            return
        }

        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in

            if let data = data,
                let rawJSON = try? JSONSerialization.jsonObject(with: data),
                let json = rawJSON as? [String: Any],
                let featuresDict = json["features"] as? [[String: Any]],
                let propertiesArray = featuresDict!["properties"] as? [String: Any] {
Error: Cannot force unwrap value of non-optional type '[[String : Any]]'

                let alertData = AlertData(json: propertiesArray)
                completion(alertData)

            } else {
                print("Either no data was returned in AlertDataController.checkWxAlert, or data was not serialized.")

                completion(nil)
                return
            }
        }

        task.resume()
    }

}

网址扩展名:

import Foundation

extension URL {

    func withQueries(_ queries: [String: String]) -> URL? {
        var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
        components?.queryItems = queries.flatMap { URLQueryItem(name: $0.0, value: $0.1) }
        return components?.url
    }

    func withHTTPS() -> URL? {
        var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
        components?.scheme = "https"
        return components?.url
    }
}

2 个答案:

答案 0 :(得分:0)

错误

  

无法强制展开非可选类型的值' [[String:Any]]'

很清楚。之所以会发生这种情况,是因为在可选的绑定表达式featuresDict在评估下一个条件时已经解包。

只需删除感叹号

即可
      ... let propertiesArray = featuresDict["properties"] as? [String: Any] {

该错误与创建URL的方式完全无关。

答案 1 :(得分:0)

如果featuresDict确实是一个数组,则不能使用featuresDict["properties"]语法。带字符串语法的下标仅适用于字典。但是你显然有一系列词典。

您可以遍历featuresDict数组(我将重命名为featuresArray以避免混淆),您可以在完成解包后执行此操作。或者,如果只想要与每个词典的properties键关联的值数组,那么flatMap可能是一个不错的选择。

例如:

let task = URLSession.shared.dataTask(with: url) { data, _, error in

    guard let data = data,
        error == nil,
        let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
        let featuresArray = json["features"] as? [[String: Any]] else {
            print("Either no data was returned in AlertDataController.checkWxAlert, or data was not serialized.")

            completion(nil)
            return
    }

    let propertiesArray = featuresArray.flatMap { $0["properties"] }
    let alertData = AlertData(json: propertiesArray)
    completion(alertData)
}

或者,如果AlertData期望每个属性本身都是字典,那么您可以这样做:

let propertiesArray = featuresArray.flatMap { $0["properties"] as? [String: Any] }

只需将AlertData在其数组json中所期望的任何类型替换为该投射。

或者,如果您只对第一个属性感兴趣,则使用first而不是flatMap