Прежде всего, никогда не загружайте данные синхронно с удаленного 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 любой 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)
}
Если корневой объект является 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)
}
Другие кодируемые источники: