Перш за все, ніколи не завантажуйте дані синхронно з віддаленої URL-адреси , використовуйте завжди такі асинхронні методи URLSession
.
"Будь-який" не має членів підписки
виникає тому, що компілятор не має уявлення про тип типу проміжних об'єктів (наприклад, currently
в ["currently"]!["temperature"]
), а оскільки ви використовуєте типи колекцій Foundation, як- NSDictionary
от компілятор, взагалі не мають уявлення про тип.
Додатково в Swift 3 потрібно повідомити компілятора про тип усіх підписаних об'єктів.
Ви повинні привести результат серіалізації JSON до фактичного типу.
Цей код використовує URLSession
і виключно рідні типи Swift
let 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
). Але зазвичай опустіть options
параметр, що означає Немає параметрів .
===================================================== ==========================
Деякі загальні міркування для розбору JSON
JSON - це добре впорядкований текстовий формат. Читати рядок JSON дуже просто. Уважно прочитайте рядок . Існує лише шість різних типів - два типи колекцій та чотири типи значень.
Типи колекцій є
- Масив - JSON: об'єкти в квадратних дужках
[]
- Swift: [Any]
але в більшості випадків[[String:Any]]
- Словник - JSON: об'єкти в фігурних дужках
{}
- Swift:[String:Any]
Типи значень є
- Рядок - JSON: будь-яке значення в подвійних лапках
"Foo"
, парне "123"
або "false"
- Swift:String
- Число - JSON: числові значення не в подвійних лапках
123
або 123.0
- Swift: Int
абоDouble
- Bool - JSON:
true
чи false
ні в подвійних лапках - Swift: true
абоfalse
- null - JSON:
null
- Swift:NSNull
Відповідно до специфікації JSON, всі словники в словниках повинні бути String
.
В основному завжди рекомендується використовувати додаткові прив'язки, щоб безпечно розгортати додаткові елементи
Якщо кореневим об'єктом є словник ( {}
), видайте його типу[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 - це просто один із типів значень - а не тип колекції - вам потрібно передати цей .allowFragments
параметр і передати результат відповідному типу значення, наприклад
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Apple опублікувала вичерпну статтю в блозі Swift: Робота з JSON в Swift
===================================================== ==========================
У Swift 4+ Codable
протокол забезпечує більш зручний спосіб розбору JSON безпосередньо на структури / класи.
Наприклад, наведений зразок JSON у запитанні (трохи модифікований)
let jsonString = """
{"icon": "partly-cloudy-night", "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precip_intensity": 0, "wind_speed": 6.04, "summary": "Partly Cloudy", "ozone": 321.13, "temperature": 49.45, "dew_point": 41.75, "apparent_temperature": 47, "wind_bearing": 332, "cloud_cover": 0.28, "time": 1480846460}
"""
можна розшифрувати в структуру Weather
. Типи Swift такі ж, як описано вище. Є кілька додаткових варіантів:
- Рядки, що представляють собою,
URL
можна декодувати безпосередньо URL
.
time
Ціле число може бути декодовано , як Date
з dateDecodingStrategy
.secondsSince1970
.
- ключі JSON snake_cased можна перетворити на camelCase за допомогою
keyDecodingStrategy
.convertFromSnakeCase
struct Weather: Decodable {
let icon, summary: String
let pressure: Double, humidity, windSpeed : Double
let ozone, temperature, dewPoint, cloudCover: Double
let precipProbability, precipIntensity, apparentTemperature, windBearing : Int
let time: Date
}
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Weather.self, from: data)
print(result)
} catch {
print(error)
}
Інші джерела, доступні