parse - swift urlsession json




Analisando corretamente o JSON no Swift 3 (5)

Em primeiro lugar, nunca carregue dados de forma síncrona a partir de um URL remoto , utilize sempre métodos assíncronos, como o URLSession .

'Qualquer' não tem 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 Foundation como NSDictionary o compilador não tem nenhuma idéia sobre o tipo.

Além disso, no Swift 3, é necessário informar o compilador sobre o tipo de todos os objetos subscritos.

Você tem que converter o resultado da serialização JSON para o tipo real.

Este código usa os tipos nativos URLSession e exclusivamente 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 chave / valor de currentConditions você pode escrever

 let currentConditions = parsedData["currently"] as! [String:Any]

  for (key, value) in currentConditions {
    print("\(key) - \(value) ")
  }

Uma nota sobre jsonObject(with data :

Muitos (parece tudo) tutoriais sugerem opções .mutableLeaves ou .mutableLeaves que são completamente absurdas no Swift. As duas opções são opções herdadas do Objective-C para atribuir o resultado a objetos NSMutable... No Swift, qualquer variável é mutável por padrão e passar qualquer uma dessas opções e atribuir o resultado a uma constante let não tem nenhum efeito. Além disso, a maioria das implementações nunca está alterando o JSON desserializado de qualquer maneira.

A única opção (rara) que é útil no Swift é .allowFragments que é necessária se o objeto raiz JSON puder ser um tipo de valor ( String , Number , Bool ou null ) em vez de um dos tipos de coleção ( array ou dictionary ). Mas normalmente omite o parâmetro options que significa que não há opções .

================================================== =========================

Algumas considerações gerais para analisar o JSON

JSON é um formato de texto bem organizado. É muito fácil ler uma string JSON. Leia a corda com cuidado . 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" , até "123" ou "false" - Swift: String
  • Number - JSON: valores numéricos que não estão entre aspas duplas 123 ou 123.0 - Swift: Int ou Double
  • Bool - JSON: true ou false não entre aspas duplas - Swift: true ou false
  • null - JSON: null - Swift: NSNull

De acordo com a especificação JSON, todas as chaves nos dicionários precisam ser String .

Basicamente, é sempre recomendado usar ligações opcionais para desembrulhar opções com segurança

Se o objeto raiz for um dicionário ( {} ), digite o tipo para [String:Any]

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...

e recuperar valores por chaves com ( OneOfSupportedJSONTypes é uma coleção JSON ou um tipo de valor, conforme descrito acima).

if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
    print(foo)
} 

Se o objeto raiz for uma matriz ( [] ), converta o tipo para [[String:Any]]

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...

e percorrer a matriz com

for item in parsedData {
    print(item)
}

Se você precisar de um item no í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)
    }
}

No caso raro de o JSON ser simplesmente um dos tipos de valor - em vez de um tipo de coleção - você precisa passar a opção .allowFragments e converter o resultado para o 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 Swift Blog: Trabalhando com JSON no Swift

================================================== =========================

No Swift 4, o protocolo Codable fornece uma maneira mais conveniente de analisar o JSON diretamente em structs / classes.

Por exemplo, o exemplo JSON fornecido na questão (ligeiramente modificado)

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 no struct Weather . Os tipos Swift são os mesmos descritos acima. Existem algumas opções adicionais:

  • Strings representando uma URL podem ser decodificadas diretamente como URL .
  • O inteiro de time pode ser decodificado como Date com o dateDecodingStrategy .secondsSince1970 .
  • snaked_cased As chaves JSON 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:

Eu estou tentando buscar uma resposta JSON e armazenar os resultados em uma variável. Eu tive versões deste código trabalhando em versões anteriores do Swift, até que a versão GM do Xcode 8 foi lançada. Eu dei uma olhada em alguns posts similares 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 ideias transmitidas não se aplicam neste cenário.

Como faço para analisar corretamente a resposta JSON no Swift 3? Alguma coisa mudou na forma como o JSON é lido no Swift 3?

Abaixo está o código em questão (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)")
        }
    }
}

Edit: Aqui está uma amostra dos resultados da chamada da API após a print(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]

Eu construí quicktype exatamente para esse propósito. Basta colar seu JSON de amostra e o quicktype gera essa hierarquia de tipos para seus dados da API:

struct Forecast {
    let hourly: Hourly
    let daily: Daily
    let currently: Currently
    let flags: Flags
    let longitude: Double
    let latitude: Double
    let offset: Int
    let timezone: String
}

struct Hourly {
    let icon: String
    let data: [Currently]
    let summary: String
}

struct Daily {
    let icon: String
    let data: [Datum]
    let summary: String
}

struct Datum {
    let precipIntensityMax: Double
    let apparentTemperatureMinTime: Int
    let apparentTemperatureLowTime: Int
    let apparentTemperatureHighTime: Int
    let apparentTemperatureHigh: Double
    let apparentTemperatureLow: Double
    let apparentTemperatureMaxTime: Int
    let apparentTemperatureMax: Double
    let apparentTemperatureMin: Double
    let icon: String
    let dewPoint: Double
    let cloudCover: Double
    let humidity: Double
    let ozone: Double
    let moonPhase: Double
    let precipIntensity: Double
    let temperatureHigh: Double
    let pressure: Double
    let precipProbability: Double
    let precipIntensityMaxTime: Int
    let precipType: String?
    let sunriseTime: Int
    let summary: String
    let sunsetTime: Int
    let temperatureMax: Double
    let time: Int
    let temperatureLow: Double
    let temperatureHighTime: Int
    let temperatureLowTime: Int
    let temperatureMin: Double
    let temperatureMaxTime: Int
    let temperatureMinTime: Int
    let uvIndexTime: Int
    let windGust: Double
    let uvIndex: Int
    let windBearing: Int
    let windGustTime: Int
    let windSpeed: Double
}

struct Currently {
    let precipProbability: Double
    let humidity: Double
    let cloudCover: Double
    let apparentTemperature: Double
    let dewPoint: Double
    let ozone: Double
    let icon: String
    let precipIntensity: Double
    let temperature: Double
    let pressure: Double
    let precipType: String?
    let summary: String
    let uvIndex: Int
    let windGust: Double
    let time: Int
    let windBearing: Int
    let windSpeed: Double
}

struct Flags {
    let sources: [String]
    let isdStations: [String]
    let units: String
}

Ele também gera código de empacotamento livre de dependência para persuadir o valor de retorno de JSONSerialization.jsonObject em uma Forecast , incluindo um construtor de conveniência que usa uma sequência JSON para que você possa analisar rapidamente um valor de Forecast fortemente digitado e acessar seus campos:

let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)

Você pode instalar o quicktype do npm com npm i -g quicktype ou usar a interface do usuário da web para obter o código gerado completo para colar no seu playground.


O problema está no método de interação da API. A análise JSON é alterada apenas na sintaxe. O principal problema é com a 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ê precisa solicitar dados por meio da API e aguardar a resposta com dados. Você pode conseguir isso com sessões de URL e bibliotecas de terceiros como o Alamofire. Abaixo está o código para o método de sessão de URL.

let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"

let url = URL.init(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
  guard error == nil else {
  print(error)
  }
  do {

    let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any] // Note if your data is coming in Array you should be using [Any]()
    //Now your data is parsed in Data variable and you can use it normally

    let currentConditions = Data["currently"] as! [String:Any]

    print(currentConditions)

    let currentTemperatureF = currentConditions["temperature"] as! Double
    print(currentTemperatureF)
  } catch let error as NSError {
    print(error)

  }

}.resume()

Uma grande mudança que aconteceu com o Xcode 8 Beta 6 para o Swift 3 foi que o id agora importa como Any ao invés de AnyObject .

Isso significa que parsedData é retornado como um dicionário mais provável com o tipo [Any:Any] . Sem usar um depurador eu não poderia dizer exatamente o que o seu elenco de NSDictionary vai fazer, mas o erro que você está vendo é porque o dict!["currently"]! tem tipo Any

Então, como você resolve isso? Do jeito que você referenciou, eu assumo o dict!["currently"]! é um dicionário e você tem muitas opções:

Primeiro você poderia fazer algo assim:

let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]  

Isso lhe dará um objeto de dicionário que você pode consultar por valores e assim você pode obter sua temperatura assim:

let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double

Ou se você preferir, pode fazê-lo na fila:

let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double

Espero que isso ajude, infelizmente não tive tempo de escrever um aplicativo de exemplo para testá-lo.

Uma nota final: a coisa mais fácil de fazer é simplesmente converter a carga JSON em [String: AnyObject] logo no início.

let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>

let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"

let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!

do {
    let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
    if let names = json["names"] as? [String] 
{
        print(names)
}
} catch let error as NSError {
    print("Failed to load: \(error.localizedDescription)")
}






xcode8