Antes de tudo, nunca carregue dados de forma síncrona de uma URL remota , use sempre métodos assíncronos como URLSession
.
'Any' não possui membros subscritos
ocorre porque o compilador não tem idéia de que tipo são os objetos intermediários (por exemplo, currently
em ["currently"]!["temperature"]
) e como você está usando tipos de coleção do Foundation, como NSDictionary
o compilador não tem idéia do tipo.
Além disso, no Swift 3, é necessário informar o compilador sobre o tipo de todos os objetos subscritos.
Você deve converter o resultado da serialização JSON no tipo real.
Este código usa URLSession
e exclusivamente tipos nativos 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()
Para imprimir todos os pares de chave / valor, currentConditions
você pode escrever
let currentConditions = parsedData["currently"] as! [String:Any]
for (key, value) in currentConditions {
print("\(key) - \(value) ")
}
Uma observação sobre jsonObject(with data
:
Muitos (parece tudo) tutoriais sugerem .mutableContainers
ou .mutableLeaves
opções que são completamente sem sentido no Swift. As duas opções são opções legadas de Objective-C para atribuir o resultado aos NSMutable...
objetos. No Swift, qualquer var
responsável é mutável por padrão e passar qualquer uma dessas opções e atribuir o resultado a uma let
constante não tem efeito algum. Além disso, a maioria das implementações nunca está alterando o JSON desserializado de qualquer maneira.
A única opção (raro) que é útil no Swift é .allowFragments
que é necessário se se o objecto raiz JSON poderia ser um tipo de valor ( String
, Number
, Bool
ou null
) em vez de um dos tipos de recolha ( array
ou dictionary
). Mas normalmente omita o options
parâmetro que significa Sem opções .
==================================================== =========================
Algumas considerações gerais para analisar JSON
JSON é um formato de texto bem organizado. É muito fácil ler uma string JSON. Leia a string com atenção . Existem apenas seis tipos diferentes - dois tipos de coleção e quatro tipos de valor.
Os tipos de coleção são
- Matriz - JSON: objetos entre colchetes
[]
- Swift: [Any]
mas na maioria dos casos[[String:Any]]
- Dicionário - JSON: objetos em chaves
{}
- Swift:[String:Any]
Os tipos de valor são
- String - JSON: qualquer valor entre aspas duplas
"Foo"
, par "123"
ou "false"
- Swift:String
- Número - JSON: valores numéricos que não estão entre aspas duplas
123
ou 123.0
- Swift: Int
ouDouble
- Bool - JSON:
true
ou false
não entre aspas duplas - Swift: true
oufalse
- null - JSON:
null
- Rápido:NSNull
De acordo com a especificação JSON, todas as chaves nos dicionários devem ser String
.
Basicamente, é sempre recomendável usar ligações opcionais para desembrulhar opcionais com segurança
Se o objeto raiz for um dicionário ( {}
), converta o tipo para[String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
e recupere valores por chaves com ( OneOfSupportedJSONTypes
é a coleção JSON ou o tipo de valor, conforme descrito acima.)
if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
print(foo)
}
Se o objeto raiz for um array ( []
), converta o tipo para[[String:Any]]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...
e percorra a matriz com
for item in parsedData {
print(item)
}
Se você precisar de um item em um índice específico, verifique também se o índice existe
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
let item = parsedData[2] as? OneOfSupportedJSONTypes {
print(item)
}
}
Nos raros casos em que o JSON é simplesmente um dos tipos de valor - em vez de um tipo de coleção - você deve passar a .allowFragments
opção e converter o resultado no tipo de valor apropriado, por exemplo
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
A Apple publicou um artigo abrangente no Blog Swift: Trabalhando com JSON no Swift
==================================================== =========================
No Swift 4+, o Codable
protocolo fornece uma maneira mais conveniente de analisar o JSON diretamente em estruturas / classes.
Por exemplo, a amostra JSON fornecida na pergunta (ligeiramente modificada)
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}
"""
pode ser decodificado na estrutura Weather
. Os tipos Swift são os mesmos descritos acima. Existem algumas opções adicionais:
- Strings representando um
URL
podem ser decodificadas diretamente como URL
.
- O
time
número inteiro pode ser decodificado como Date
com o dateDecodingStrategy
.secondsSince1970
.
- As chaves JSON snaked_cased podem ser convertidas em camelCase com o
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)
}
Outras fontes codificáveis: