Estou tentando buscar uma resposta JSON e armazenar os resultados em uma variável. Eu tive versões desse código funcionando em versões anteriores do Swift, até que a versão GM do Xcode 8 foi lançada. Dei uma olhada em algumas postagens semelhantes no StackOverflow: Swift 2 Parsing JSON - Não é possível subscrever um valor do tipo 'AnyObject' e JSON Parsing no Swift 3 .
No entanto, parece que as idéias transmitidas não se aplicam nesse cenário.
Como analiso corretamente a resposta JSON no Swift 3? Alguma coisa mudou na maneira como o JSON é lido no Swift 3?
Abaixo está o código em questão (ele pode ser executado em um playground):
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)")
}
}
}
Editar: Aqui está uma amostra dos resultados da chamada da API apósprint(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]
Respostas:
Antes de tudo, nunca carregue dados de forma síncrona de uma URL remota , use sempre métodos assíncronos como
URLSession
.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, comoNSDictionary
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 SwiftPara imprimir todos os pares de chave / valor,
currentConditions
você pode escreverUma 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 aosNSMutable...
objetos. No Swift, qualquervar
responsável é mutável por padrão e passar qualquer uma dessas opções e atribuir o resultado a umalet
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
ounull
) em vez de um dos tipos de recolha (array
oudictionary
). Mas normalmente omita ooptions
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
[]
- Swift:[Any]
mas na maioria dos casos[[String:Any]]
{}
- Swift:[String:Any]
Os tipos de valor são
"Foo"
, par"123"
ou"false"
- Swift:String
123
ou123.0
- Swift:Int
ouDouble
true
oufalse
não entre aspas duplas - Swift:true
oufalse
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]
e recupere valores por chaves com (
OneOfSupportedJSONTypes
é a coleção JSON ou o tipo de valor, conforme descrito acima.)Se o objeto raiz for um array (
[]
), converta o tipo para[[String:Any]]
e percorra a matriz com
Se você precisar de um item em um índice específico, verifique também se o índice existe
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 exemploA 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)
pode ser decodificado na estrutura
Weather
. Os tipos Swift são os mesmos descritos acima. Existem algumas opções adicionais:URL
podem ser decodificadas diretamente comoURL
.time
número inteiro pode ser decodificado comoDate
com odateDecodingStrategy
.secondsSince1970
.keyDecodingStrategy
.convertFromSnakeCase
Outras fontes codificáveis:
fonte
dict!["currently"]!
um dicionário no qual o compilador possa inferir com segurança a assinatura de chave subsequente.Uma grande mudança que aconteceu com o Xcode 8 Beta 6 para o Swift 3 foi que o id agora importa mais
Any
do queAnyObject
.Isso significa que
parsedData
é retornado como um dicionário do mais provável com o tipo[Any:Any]
. Sem usar um depurador, eu não poderia dizer exatamente o que seu elencoNSDictionary
fará, mas o erro que você está vendo é porquedict!["currently"]!
tem o tipoAny
Então, como você resolve isso? Pela maneira como você o referenciou, presumo que
dict!["currently"]!
seja um dicionário e, portanto, você tem muitas opções:Primeiro, você poderia fazer algo assim:
Isso fornecerá um objeto de dicionário que você poderá consultar por valores e obter sua temperatura assim:
Ou, se você preferir, pode fazê-lo na linha:
Espero que isso ajude, receio que não tenha tido tempo de escrever um aplicativo de amostra para testá-lo.
Uma observação final: a coisa mais fácil a se fazer, pode ser simplesmente converter a carga JSON
[String: AnyObject]
no início.fonte
dict!["currently"]! as! [String: String]
irá travar[String: String]
não pode funcionar porque existem alguns valores numéricos. Não funciona em um Mac Playgroundfonte
Eu construí QuickType exatamente para esta finalidade. Basta colar seu JSON de amostra e o quicktype gera essa hierarquia de tipos para seus dados da API:
Ele também gera código de empacotamento livre de dependência para persuadir o valor de retorno de
JSONSerialization.jsonObject
para aForecast
, incluindo um construtor de conveniência que usa uma string JSON para que você possa analisar rapidamente umForecast
valor fortemente digitado e acessar seus campos:Você pode instalar o quicktype a partir do npm com
npm i -g quicktype
ou usar a interface da web para obter o código completo gerado para colar no seu playground.fonte
Atualizado os
isConnectToNetwork-Function
depois, graças a este cargo .Eu escrevi um método extra para isso:
Então agora você pode facilmente chamar isso em seu aplicativo onde quiser
fonte
Swift tem uma inferência de tipo poderosa. Vamos nos livrar do clichê "if let" ou "guard let" e forçar o desembrulho usando a abordagem funcional:
Apenas uma linha de código e sem forçar a abertura ou a conversão manual de tipos. Esse código funciona no playground, para que você possa copiá-lo e verificá-lo. Aqui está uma implementação no GitHub.
fonte
Essa é outra maneira de resolver seu problema. Então, confira a solução abaixo. Espero que ajude você.
fonte
O problema está no método de interação da API. A análise JSON é alterada apenas na sintaxe. O principal problema está na maneira de buscar dados. O que você está usando é uma maneira síncrona de obter dados. Isso não funciona em todos os casos. O que você deve usar é uma maneira assíncrona de buscar dados. Dessa forma, você deve solicitar dados por meio da API e aguardar a resposta dos dados. Você pode conseguir isso com sessões de URL e bibliotecas de terceiros como
Alamofire
. Abaixo está o código para o método de sessão de URL.fonte
Se eu criar um modelo usando o json anterior Usando este link [blog]: http://www.jsoncafe.com para gerar estrutura Codable ou Qualquer Formato
Modelo
Analisar
fonte