Прежде всего, никогда не загружайте данные синхронно с удаленного URL-адреса , всегда используйте асинхронные методы, например URLSession.
'Any' не имеет нижних индексов
происходит потому, что компилятор не знает, к какому типу относятся промежуточные объекты (например, 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 любой variable является изменяемым по умолчанию, и передача любого из этих параметров и присвоение результата 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)
}
Если корневой объект является array ( []), приведите тип к[[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.
- snaked_cased ключи JSON могут быть преобразованы в 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)
}
Другие кодируемые источники: