Корректный разбор JSON в Swift 3
Я пытаюсь получить ответ JSON и сохранить результаты в переменной. У меня были версии этого кода в предыдущих версиях Swift, пока не была выпущена версия GM Xcode 8. Я посмотрел на несколько подобных сообщений на StackOverflow: Swift 2 Parsing JSON - не может индексировать значение типа "AnyObject" и JSON Parsing в Swift 3.
однако, похоже, что идеи, переданные там, не применяются в этом сценарии.
Как правильно разобрать JSON ответ в Swift 3? Что-то изменилось в способе чтения JSON в Swift 3?
Ниже приведен код в вопросе (он может быть запущен на детской площадке):
import Cocoa
let url = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
if let url = NSURL(string: url) {
if let data = try? Data(contentsOf: url as URL) {
do {
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)
//Store response in NSDictionary for easy access
let dict = parsedData as? NSDictionary
let currentConditions = "(dict!["currently"]!)"
//This produces an error, Type 'Any' has no subscript members
let currentTemperatureF = ("(dict!["currently"]!["temperature"]!!)" as NSString).doubleValue
//Display all current conditions from API
print(currentConditions)
//Output the current temperature in Fahrenheit
print(currentTemperatureF)
}
//else throw an error detailing what went wrong
catch let error as NSError {
print("Details of JSON parsing error:n (error)")
}
}
}
Edit: вот пример результатов от вызова API после print(currentConditions)
["icon": partly-cloudy-night, "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precipIntensity": 0, "windSpeed": 6.04, "summary": Partly Cloudy, "ozone": 321.13, "temperature": 49.45, "dewPoint": 41.75, "apparentTemperature": 47, "windBearing": 332, "cloudCover": 0.28, "time": 1480846460]
6 ответов:
в первую очередь никогда не загружайте данные синхронно с удаленного URL, используйте всегда асинхронные методы, такие как
URLSession
.' Any ' не имеет подписчиков
происходит потому, что компилятор понятия не имеет, какого типа промежуточные объекты (например
currently
на["currently"]!["temperature"]
) и поскольку вы используете типы коллекций Foundation, такие какNSDictionary
компилятор понятия не имеет о типе вообще.дополнительно в Swift 3 требуется сообщить компилятору о типе все индексного объектов.
вы должны привести результат сериализации JSON к фактическому типу.
этот код использует
URLSession
и исключительно Swift native typeslet urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951" let url = URL(string: urlString) URLSession.shared.dataTask(with:url!) { (data, response, error) in if error != nil { print(error) } else { do { let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any] let currentConditions = parsedData["currently"] as! [String:Any] print(currentConditions) let currentTemperatureF = currentConditions["temperature"] as! Double print(currentTemperatureF) } catch let error as NSError { print(error) } } }.resume()
для печати всех пар ключ / значение
currentConditions
вы могли бы написатьlet currentConditions = parsedData["currently"] as! [String:Any] for (key, value) in currentConditions { print("\(key) - \(value) ") }
Примечание относительно
jsonObject(with data
:многие (кажется, все) учебники предлагают
.mutableContainers
или.mutableLeaves
параметры, которые совершенно бессмысленны в Swift. Эти два варианта являются устаревшими параметрами Objective-C, чтобы назначить результатNSMutable...
объекты. В Swift какие-либоvar
iable по умолчанию является изменяемым и передает любой из этих параметров и присваивает результатlet
постоянная, не имеет никакого эффекта вообще. Кроме того, большинство реализаций никогда не мутируют десериализованный JSON в любом случае.в единственный (редкий) вариант, который полезен в Swift-это
.allowFragments
что требуется, если корневой объект JSON может быть типом значения (String
,Number
,Bool
илиnull
) вместо одного из типов коллекций (array
илиdictionary
). Но, как правило, не указывать - JSON: числовые значения не в двойные кавычки123
или123.0
– Swift:Int
илиDouble
Буль - JSON: true
илиfalse
не в двойных кавычках-Swift:true
илиfalse
null - JSON: null
– Swift:NSNull
согласно спецификации JSON все ключи в словарях должны быть
String
.
в основном это всегда рекомендуется использовать дополнительные привязки, чтобы развернуть optionals безопасно
если корневой объект является словарем (
{}
) привести тип к[String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
и получить значения по ключам с (
OneOfSupportedJSONTypes
- это либо коллекция JSON, либо тип значения, как описано выше.)if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes { print(foo) }
если корневой объект является массивом (
[]
) приведите тип к[[String:Any]]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...
и перебирать массив с
for item in parsedData { print(item) }
Если вам нужен элемент в определенном индексе, проверьте также, существует ли индекс
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2, let item = parsedData[2] as? OneOfSupportedJSONTypes { print(item) } }
в том редком случае, что JSON просто один из типов значений-а не тип коллекции – вы должны передать и привести результат к соответствующему типу значения, например
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Apple опубликовала исчерпывающую статью в блоге Swift: работа с JSON в Swift
большое изменение, которое произошло с Xcode 8 Beta 6 для Swift 3, заключалось в том, что id теперь импортируется как
Any
, а неAnyObject
.это означает, что
parsedData
возвращается в виде словаря скорее всего с типом[Any:Any]
. Без использования отладчика я не мог сказать вам точно, что ваш бросок кNSDictionary
будет делать, но ошибка, которую вы видите, потому чтоdict!["currently"]!
типаAny
Итак, как вы решаете эту проблему? Из того, как вы ссылались на него, я предполагаю
dict!["currently"]!
- это словарь и так у вас есть много вариантов:во-первых, вы могли бы сделать что-то вроде этого:
let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]
это даст вам объект словаря, который вы можете запросить значения, и поэтому вы можете получить свою температуру следующим образом:
let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double
или если вы предпочитаете, вы можете сделать это в строку:
let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double
надеюсь, это поможет, я боюсь, что у меня не было времени, чтобы написать пример приложения, чтобы проверить его.
последнее примечание: самый простой что нужно сделать, может быть, просто бросить полезную нагрузку JSON в
[String: AnyObject]
в самом начале.let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}" let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)! do { let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject] if let names = json["names"] as? [String] { print(names) } } catch let error as NSError { print("Failed to load: \(error.localizedDescription)") }
Обновлено isConnectToNetwork-функция впоследствии, благодаря этому сообщению проверьте подключение к интернету с помощью Swift
Я написал дополнительный метод для этого:
import SystemConfiguration func loadingJSON(_ link:String, postString:String, completionHandler: @escaping (_ JSONObject: AnyObject) -> ()) { if(isConnectedToNetwork() == false){ completionHandler("-1" as AnyObject) return } let request = NSMutableURLRequest(url: URL(string: link)!) request.httpMethod = "POST" request.httpBody = postString.data(using: String.Encoding.utf8) let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in guard error == nil && data != nil else { // check for fundamental networking error print("error=\(error)") return } if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 { // check for http errors print("statusCode should be 200, but is \(httpStatus.statusCode)") print("response = \(response)") } //JSON successfull do { let parseJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) DispatchQueue.main.async(execute: { completionHandler(parseJSON as AnyObject) }); } catch let error as NSError { print("Failed to load: \(error.localizedDescription)") } } task.resume() } func isConnectedToNetwork() -> Bool { var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) zeroAddress.sin_family = sa_family_t(AF_INET) let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) { .withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress) } } var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0) if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false { return false } let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0 let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0 let ret = (isReachable && !needsConnection) return ret }
Так что теперь вы можете легко назвать это в вашем приложении, где вы хотите
loadingJSON("yourDomain.com/login.php", postString:"email=\(userEmail!)&password=\(password!)") { parseJSON in if(String(describing: parseJSON) == "-1"){ print("No Internet") } else { if let loginSuccessfull = parseJSON["loginSuccessfull"] as? Bool { //... do stuff } }
Я построил quicktype именно для этой цели. Просто вставьте образец JSON и quicktype генерирует эту иерархию типов для ваших данных API:
struct Forecast { let hourly: Hourly let daily: Daily let currently: Currently let flags: Flags let longitude: Double let latitude: Double let offset: Int let timezone: String } struct Hourly { let icon: String let data: [Currently] let summary: String } struct Daily { let icon: String let data: [Datum] let summary: String } struct Datum { let precipIntensityMax: Double let apparentTemperatureMinTime: Int let apparentTemperatureLowTime: Int let apparentTemperatureHighTime: Int let apparentTemperatureHigh: Double let apparentTemperatureLow: Double let apparentTemperatureMaxTime: Int let apparentTemperatureMax: Double let apparentTemperatureMin: Double let icon: String let dewPoint: Double let cloudCover: Double let humidity: Double let ozone: Double let moonPhase: Double let precipIntensity: Double let temperatureHigh: Double let pressure: Double let precipProbability: Double let precipIntensityMaxTime: Int let precipType: String? let sunriseTime: Int let summary: String let sunsetTime: Int let temperatureMax: Double let time: Int let temperatureLow: Double let temperatureHighTime: Int let temperatureLowTime: Int let temperatureMin: Double let temperatureMaxTime: Int let temperatureMinTime: Int let uvIndexTime: Int let windGust: Double let uvIndex: Int let windBearing: Int let windGustTime: Int let windSpeed: Double } struct Currently { let precipProbability: Double let humidity: Double let cloudCover: Double let apparentTemperature: Double let dewPoint: Double let ozone: Double let icon: String let precipIntensity: Double let temperature: Double let pressure: Double let precipType: String? let summary: String let uvIndex: Int let windGust: Double let time: Int let windBearing: Int let windSpeed: Double } struct Flags { let sources: [String] let isdStations: [String] let units: String }
он также генерирует код маршалинга без зависимостей, чтобы уговорить возвращаемое значение
JSONSerialization.jsonObject
наForecast
, включая конструктор удобства, который принимает строку JSON, чтобы вы могли быстро проанализировать строго типизированныйForecast
значение и доступ к его полям:let forecast = Forecast.from(json: jsonString)! print(forecast.daily.data[0].windGustTime)
вы можете установить quicktype из npm с
npm i -g quicktype
или используйте веб-интерфейс чтобы получить полный сгенерированный код для вставки на детской площадке.
проблема заключается в методе взаимодействия API.Синтаксический анализ JSON изменяется только в синтаксисе. Основная проблема заключается в способе извлечения данных. Что вы используете синхронный способ передачи данных. Это не работает в каждом случае. Что вы должны использовать асинхронный способ получения данных. Таким образом, вы должны запросить данные через API и ждать, пока он ответит данными. Вы можете достичь этого с помощью сеанса URL и сторонних библиотек, таких как Alamofire. Ниже приведен код для Метод сеанс URL-адрес.
let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951" let url = URL.init(string: urlString) URLSession.shared.dataTask(with:url!) { (data, response, error) in guard error == nil else { print(error) } do { let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any] // Note if your data is coming in Array you should be using [Any]() //Now your data is parsed in Data variable and you can use it normally let currentConditions = Data["currently"] as! [String:Any] print(currentConditions) let currentTemperatureF = currentConditions["temperature"] as! Double print(currentTemperatureF) } catch let error as NSError { print(error) } }.resume()