Grande parte dos aplicativos atuais utilizam algum tipo de informação extraída da WEB. Por padrão a maioria das fontes de dados utilizam o formato JSON (JavaScript Object Notation). Ele ele se tornou popular devido a simplicidade e a fácil leitura.
Utilizando JSON com Swift
Considere o JSON abaixo.
[{
"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [{
"value": "New",
"onclick": "CreateNewDoc()"
}, {
"value": "Open",
"onclick": "OpenDoc()"
}, {
"value": "Close",
"onclick": "CloseDoc()"
}]
}
}
}]
Trabalhar com JSON no Objective-C é uma tarefa simples.
NSArray *json = [NSJSONSerialization JSONObjectWithData:JSONData options:kNilOptions error:nil];
NSString *value = json[0][@"menu"][@"value"];
NSLog(@"The menu value is: %@", value);
No Swift a tarefa se torna mais trabalhosa em função dos optionals
e type-safety
.
var json: [[String: AnyObject]]!
do {
json = try NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions()) as? [[String: AnyObject]]
} catch {
print(error)
}
let item = json[0]
if let menu = item["menu"] as? [String: AnyObject] {
if let value = menu["value"] as? String {
print("The menu value is: \(value)")
}
}
No código acima vimos que no Swift temos que verificar os valores com optional binding
o que torna o código meio confuso principalmente se a fonte de dados tiver muitos níveis.
No Swift 2.0 foi introduzido o comando guard
que tornou a tarefa um pouco menos complexa retirando os if
s aninhados.
var json: [[String: AnyObject]]!
do {
json = try NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions()) as? [[String: AnyObject]]
} catch {
print(error)
}
guard let menu = item["menu"] as? [String: AnyObject] else { return }
guard let value = menu["value"] as? String else { return }
print("The menu value is: \(value)")
Mesmo com o comando guard temos a impressão que poderia ser mais simples. Utilizei a biblioteca open source Gloss em um projeto e recentemente conheci a biblioteca Unbox que é a que iremos trabalhar hoje.
Criando o projeto
Crie um novo projeto iOS Single View Application no Xcode.

Iremos criar o projeto OpenWeatherMap utilizando Swift para iPhone.

Neste projeto iremos utilizar a fonte de dados metereológica do site OpenWeatherMap. Registre-se no site para gerar uma chave de acesso a API.
A API irá retornar um JSON com a seguinte estrutura.
{
"coord": {
"lon": -43.94,
"lat": -19.92
},
"weather": [{
"id": 500,
"main": "Rain",
"description": "chuva fraca",
"icon": "10n"
}],
"base": "cmc stations",
"main": {
"temp": 19.77,
"pressure": 923.85,
"humidity": 89,
"temp_min": 19.77,
"temp_max": 19.77,
"sea_level": 1026.66,
"grnd_level": 923.85
},
"wind": {
"speed": 1.51,
"deg": 90.5001
},
"rain": {
"3h": 0.355
},
"clouds": {
"all": 44
},
"dt": 1456536694,
"sys": {
"message": 0.0028,
"country": "BR",
"sunrise": 1456563156,
"sunset": 1456608248
},
"id": 3470127,
"name": "Belo Horizonte",
"cod": 200
}
Vamos começar o nosso projeto criando as constantes que serão utilizadas na chamada da API. Adicione a struct
abaixo na classe ViewController
.
private struct Constants
{
static let APIKey = "MyAPIKey"
static let APIAddress = "http://api.openweathermap.org/data/2.5/"
static let CityName = "MyCity,BR"
static let language = "pt"
static let units = "metric"
}
Altere as propriedades APIKey
e CityName
com a chave gerada pelo site e pela sua localidade respectivamente.
Vamos criar a função fetchOpenWeatherMapData
para consumir a API.
private func fetchOpenWeatherMapData()
{
if let url = MakeURL() {
let session = NSURLSession.sharedSession()
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
let dataTask = session.dataTaskWithURL(url) { (data, response, error) in
if let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode == 200,
let data = data {
dispatch_async(dispatch_get_main_queue()) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
self.showResults(data)
}
} else {
dispatch_async(dispatch_get_main_queue()) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
print("Error: \(error)")
if let data = data,
let dataAsString = NSString(data: data, encoding: NSUTF8StringEncoding) {
print("Data: \(dataAsString)")
}
}
}
}
dataTask.resume()
}
}
Utilizamos a classe NSURLSession
para realizar a chamada da API e caso tenhamos sucesso chamaremos o método showResults
a ser implementado a seguir.
Primeiramente vamos implementar o método MakeURL
para gerar a URL da API.
private func MakeURL() -> NSURL?
{
let URLString = "\(Constants.APIAddress)weather?APPID=\(Constants.APIKey)&q=\(Constants.CityName)&lang=\(Constants.language)&units=\(Constants.units)"
if let URLString = URLString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
if let url = NSURL(string: URLString) {
return url
}
}
return nil
}
O metodo monta um objeto NSURL
com os parâmetros definidos na struct
Constants
.
Agora vamos implementar o método showResults
.
private func showResults(data: NSData)
{
if let dataAsString = NSString(data: data, encoding: NSUTF8StringEncoding) {
print(dataAsString)
}
}
O metodo converte o objeto NSData
recebido no método fetchOpenWeatherMapData
o convertendo para String
.
Rode o projeto e verifique o JSON retornado no console de log.
Utilizando a biblioteca Unbox
Para instalar a biblioteca utilizaremos o CocoaPods. Já falei sobre ele neste post.
Execute o pod init
no diretório do projeto e adicione a referencia a biblioteca Unbox
conforme código abaixo.
platform :ios, '8.0'
use_frameworks!
target 'Forecast' do
pod 'Unbox'
end
Execute o pod install
, abra o workspace e adicione o fonte OpenWeatherData.swift
com o fonte abaixo.
import Foundation
import Unbox
class OpenWeatherData: Unboxable
{
let clouds: Double // Cloudiness, %
let country: String // Country code (GB, JP etc.)
let city: String // City name
required init(unboxer: Unboxer)
{
self.clouds = unboxer.unbox("clouds.all", isKeyPath: true)
self.country = unboxer.unbox("sys.country", isKeyPath: true)
self.city = unboxer.unbox("name")
}
}
Para instanciar um objeto com o conteúdo de um dado JSON basta implementar a interface Unboxable
. Para isto vamos importar a biblioteca Unbox
.
import Unbox
Implementar um construtor que recebe o objeto Unboxer
.
required init(unboxer: Unboxer)
{
self.clouds = unboxer.unbox("clouds.all", isKeyPath: true)
self.country = unboxer.unbox("sys.country", isKeyPath: true)
self.city = unboxer.unbox("name")
}
Para cada propriedade da classe temos que atribuir a chave correspondente no JSON.
self.city = unboxer.unbox("name")
Também podemos atribuir chaves em estruturas aninhadas utilizando pontos para compor o caminho da chave. Nestes casos devemos passar o segundo parâmetro do método unbox
(isKeyPath
) como true
.
self.clouds = unboxer.unbox("clouds.all", isKeyPath: true)
Agora vamos modificar o método showResults
para utilizar a nossa classe.
private func showResults(data: NSData)
{
if let openWeatherData: OpenWeatherData = Unbox(data) {
print("\(openWeatherData.clouds)")
print("\(openWeatherData.country)")
print("\(openWeatherData.city)")
}
}
Além do modo optional binding
utilizado acima podemos utilizar o modo error handling
do Swift 2.
private func showResults(data: NSData)
{
do {
let openWeatherData: OpenWeatherData = try UnboxOrThrow(data)
print("\(openWeatherData.clouds)")
print("\(openWeatherData.country)")
print("\(openWeatherData.city)")
} catch UnboxError.InvalidData {
print("Invalid data.")
} catch UnboxError.MissingKey(let key) {
print("MIssing key '\(key)'.")
} catch UnboxError.InvalidValue(let key, let valueDescription) {
print("Invalid value '\(valueDescription)' for key '\(key)'.")
} catch UnboxError.CustomUnboxingFailed {
print("A custom unboxing closure returned nil")
} catch {
print("Error")
}
}
A biblioteca Unbox
suporta os seguintes tipos de dados: Bool
, Int
, Double
, Float
, String
, Array
e Dictionary
.
O tipo Enum
também pode ser utilizado diretamente com o Unbox
. Basta utilizar o protocolo UnboxableEnum
.
enum WeatherIcon: String, UnboxableEnum
{
case ClearSkyDay = "01d"
case ClearSkyNight = "01n"
case FewCloudsDay = "02d"
case FewCloudsNight = "02n"
case ScatteredCloudsDay = "03d"
case ScatteredCloudsNight = "03n"
case BrokenCloudsDay = "04d"
case BrokenCloudsNight = "04n"
case ShowerRainDay = "09d"
case ShowerRainNight = "09n"
case RainDay = "10d"
case RainNight = "10n"
case ThunderstormDay = "11d"
case ThunderstormNight = "11n"
case SnowDay = "13d"
case SnowNight = "13n"
case MistDay = "50d"
case MistNight = "50n"
static func unboxFallbackValue() -> WeatherIcon
{
return self.ClearSkyDay
}
}
E também podemos utilizar tipos complexos com o Unbox
bastando apenas implementar o protocolo Unboxable
nos struct
s e nas class
es. Vamos complementar a nossa classe OpenWeatherData
.
import Foundation
import Unbox
class OpenWeatherData: Unboxable
{
let coord: Coordinate
let weather: [Weather] // (more info Weather condition codes)
let base: String // Internal parameter
let wind: Wind
let clouds: Double // Cloudiness, %
let rain: Double? // Rain volume for the last 3 hours
let snow: Double? // Snow volume for the last 3 hours
let date: Double // Time of data calculation, unix, UTC
let country: String // Country code (GB, JP etc.)
let sunrise: Double // Sunrise time, unix, UTC
let sunset: Double // Sunset time, unix, UTC
let cityId: Int // City ID
let city: String // City name
required init(unboxer: Unboxer)
{
self.coord = unboxer.unbox("coord")
self.weather = unboxer.unbox("weather")
self.base = unboxer.unbox("base")
self.wind = unboxer.unbox("wind")
self.clouds = unboxer.unbox("clouds.all", isKeyPath: true)
self.rain = unboxer.unbox("rain.3h", isKeyPath: true)
self.snow = unboxer.unbox("snow.3h", isKeyPath: true)
self.date = unboxer.unbox("dt")
self.country = unboxer.unbox("sys.country", isKeyPath: true)
self.sunrise = unboxer.unbox("sys.sunrise", isKeyPath: true)
self.sunset = unboxer.unbox("sys.sunset", isKeyPath: true)
self.cityId = unboxer.unbox("id")
self.city = unboxer.unbox("name")
}
struct Coordinate: Unboxable
{
let lon: Double // City geo location, longitude
let lat: Double // City geo location, latitude
init(unboxer: Unboxer)
{
self.lon = unboxer.unbox("lon")
self.lat = unboxer.unbox("lat")
}
}
struct Weather: Unboxable
{
let id:Int // Weather condition id
let group: String // Group of weather parameters (Rain, Snow, Extreme etc.)
let weatherDescription: String // Weather condition within the group
let icon: WeatherIcon // Weather icon id
init(unboxer: Unboxer)
{
self.id = unboxer.unbox("id")
self.group = unboxer.unbox("main")
self.weatherDescription = unboxer.unbox("description")
self.icon = unboxer.unbox("icon")
}
}
struct WeatherData: Unboxable
{
let temp: Double // Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
let pressure: Double // Atmospheric pressure (on the sea level, if there is no sea_level or grnd_level data), hPa
let humidity: Double // Humidity, %
let temp_min: Double // Minimum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
let temp_max: Double // Maximum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
let sea_level: Double // Atmospheric pressure on the sea level, hPa
let grnd_level: Double // Atmospheric pressure on the ground level, hPa
init(unboxer: Unboxer)
{
self.temp = unboxer.unbox("temp")
self.pressure = unboxer.unbox("pressure")
self.humidity = unboxer.unbox("humidity")
self.temp_min = unboxer.unbox("temp_min")
self.temp_max = unboxer.unbox("temp_max")
self.sea_level = unboxer.unbox("sea_level")
self.grnd_level = unboxer.unbox("grnd_level")
}
}
struct Wind: Unboxable
{
let speed: Double // Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour.
let deg: Double // Wind direction, degrees (meteorological)
init(unboxer: Unboxer)
{
self.speed = unboxer.unbox("speed")
self.deg = unboxer.unbox("deg")
}
}
enum WeatherIcon: String, UnboxableEnum
{
case ClearSkyDay = "01d"
case ClearSkyNight = "01n"
case FewCloudsDay = "02d"
case FewCloudsNight = "02n"
case ScatteredCloudsDay = "03d"
case ScatteredCloudsNight = "03n"
case BrokenCloudsDay = "04d"
case BrokenCloudsNight = "04n"
case ShowerRainDay = "09d"
case ShowerRainNight = "09n"
case RainDay = "10d"
case RainNight = "10n"
case ThunderstormDay = "11d"
case ThunderstormNight = "11n"
case SnowDay = "13d"
case SnowNight = "13n"
case MistDay = "50d"
case MistNight = "50n"
static func unboxFallbackValue() -> WeatherIcon
{
return self.ClearSkyDay
}
}
}
Note o uso de array
s.
let weather: [Weather] // (more info Weather condition codes)
E de opcionais.
let rain: Double? // Rain volume for the last 3 hours
let snow: Double? // Snow volume for the last 3 hours
Atualizando o metodo showResults
para incluir os novos campos.
private func showResults(data: NSData)
{
let formatter = NSNumberFormatter()
formatter.maximumFractionDigits = 2
formatter.locale = NSLocale.currentLocale()
formatter.numberStyle = .DecimalStyle
do {
let openWeatherData: OpenWeatherData = try UnboxOrThrow(data)
print("Coordinate:\n\tLatitude: \(openWeatherData.coord.lat)\n\tLongitude: \(openWeatherData.coord.lon)")
for w in openWeatherData.weather {
print("Weather:")
print("\tID: \(w.id)")
print("\tGroup: \(w.group)")
print("\tDescription: \(w.weatherDescription)")
switch (w.icon) {
case .ClearSkyDay: print("\tIcon: Clear Sky Day [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.ClearSkyDay.rawValue).png]")
case .ClearSkyNight: print("\tIcon: Clear Sky Night [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.ClearSkyNight.rawValue).png]")
case .FewCloudsDay: print("\tIcon: Few Clouds Day [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.FewCloudsDay.rawValue).png]")
case .FewCloudsNight: print("\tIcon: Few Clouds Night [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.FewCloudsNight.rawValue).png]")
case .ScatteredCloudsDay: print("\tIcon: Scattered Clouds Day [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.ScatteredCloudsDay.rawValue).png]")
case .ScatteredCloudsNight: print("\tIcon: Scattered Clouds Night [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.ScatteredCloudsNight.rawValue).png]")
case .BrokenCloudsDay: print("\tIcon: Broken Clouds Day [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.BrokenCloudsDay.rawValue).png]")
case .BrokenCloudsNight: print("\tIcon: Broken Clouds Night [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.BrokenCloudsNight.rawValue).png]")
case .ShowerRainDay: print("\tIcon: Shower Rain Day [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.ShowerRainDay.rawValue).png]")
case .ShowerRainNight: print("\tIcon: Shower Rain Night [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.ShowerRainNight.rawValue).png]")
case .RainDay: print("\tIcon: Rain Day [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.RainDay.rawValue).png]")
case .RainNight: print("\tIcon: Rain Night [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.RainNight.rawValue).png]")
case .ThunderstormDay: print("\tIcon: Thunderstorm Day [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.ThunderstormDay.rawValue).png]")
case .ThunderstormNight: print("\tIcon: Thunderstorm Night [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.ThunderstormNight.rawValue).png]")
case .SnowDay: print("\tIcon: Snow Day [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.SnowDay.rawValue).png]")
case .SnowNight: print("\tIcon: Snow Night [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.SnowNight.rawValue).png]")
case .MistDay: print("\tIcon: Mist Day [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.MistDay.rawValue).png]")
case .MistNight: print("\tIcon: Mist Night [http://openweathermap.org/img/w/\(OpenWeatherData.WeatherIcon.MistNight.rawValue).png]")
}
}
print("Base: \(openWeatherData.base)")
print("Weather Data:")
print("\tTemperature: \(formatter.stringFromNumber(openWeatherData.main.temp)!) °C")
print("\tPressure: \(formatter.stringFromNumber(openWeatherData.main.pressure)!) hPa")
print("\tHumidity: \(formatter.stringFromNumber(openWeatherData.main.humidity)!) %")
print("\tMinimun: \(formatter.stringFromNumber(openWeatherData.main.temp_min)!) °C")
print("\tMaximun: \(formatter.stringFromNumber(openWeatherData.main.temp_max)!) °C")
print("\tSea Level: \(formatter.stringFromNumber(openWeatherData.main.sea_level)!) hPa")
print("\tGround Level \(formatter.stringFromNumber(openWeatherData.main.grnd_level)!) hPa")
print("Wind:")
print("\tSpeed: \(formatter.stringFromNumber(openWeatherData.wind.speed)!) m/s")
print("\tDirection: \(formatter.stringFromNumber(openWeatherData.wind.deg)!)°")
print("Cloudiness: \(formatter.stringFromNumber(openWeatherData.clouds)!) %")
if let rain = openWeatherData.rain {
print("Rain: \(rain) mm")
}
if let snow = openWeatherData.snow {
print("Snow: \(snow)")
}
print("Date: \(NSDateFormatter.localizedStringFromDate(NSDate(timeIntervalSince1970: openWeatherData.date), dateStyle: .ShortStyle, timeStyle: .ShortStyle))")
print("Country: \(openWeatherData.country)")
print("Sunrise: \(NSDateFormatter.localizedStringFromDate(NSDate(timeIntervalSince1970: openWeatherData.sunrise), dateStyle: .NoStyle, timeStyle: .ShortStyle))")
print("Sunset: \(NSDateFormatter.localizedStringFromDate(NSDate(timeIntervalSince1970: openWeatherData.sunset), dateStyle: .NoStyle, timeStyle: .ShortStyle))")
print("City ID: \(openWeatherData.cityId)")
print("City: \(openWeatherData.city)")
} catch UnboxError.InvalidData {
print("Invalid data.")
} catch UnboxError.MissingKey(let key) {
print("MIssing key '\(key)'.")
} catch UnboxError.InvalidValue(let key, let valueDescription) {
print("Invalid value '\(valueDescription)' for key '\(key)'.")
} catch UnboxError.CustomUnboxingFailed {
print("A custom unboxing closure returned nil")
} catch {
print("Error")
}
}
Verifique na pagina do GitHub do projeto sobre o uso com os tipos NSURL
, e NSDate
com NSDateFormatter
.
Também podemos implementar a transformação em nossos próprios tipos utilizando o protocol
UnboxableByTransform
.
Utilizando Unbox
eliminamos todos aqueles if
s aninhados e guard
s para popular nossos model
s com conteúdo JSON, deixando o código bem mais limpo e legível.
Além das bibliotecas citadas aqui (Unbox e Gloss) vale a pena dar uma olhada nas bibliotecas SwiftyJSON, Argo e JSONModel para decidir qual se adapta melhor ao seu código.
Espero que tenham gostado deste post. Por favor deixe seu comentário abaixo com dúvidas, criticas e sugestões para novos posts.
Um comentário em “Usando Unbox para decodificar JSON”