Convert JSON to Custom Objects using SWIFT

openweathermap weather data view

In previous post I showed some sample code showing how to execute a GET HTTP request to get weather information from OpenWeatherMap API. Once you have successfully executed a GET request and obtained response server, next step is to process the response data. In this post I will show some sample code that demonstrates how to use NSJSONSerialization class to parse JSON data into a custom SWIFT object.

JSON serialization in iOS

If you are coming from development environment like C#, then you are used to using third party libraries like Json.Net that use reflection to convert JSON to objects. In iOS/SWIFT you do not have many choices available at the moment. Apple has provided NSJSONSerialization class that can deserialize JSON data into NSDictionary object. There is an opensource library SwiftyJSON that provides some utility support to access members from dictionary in a more typesafe manner. But you still have to do some manual work in constructing your custom Swift object from this dictionary object. In the sample code for this post, I have created OpenWeatherMapData class. The propery of this class represent weather data that you will get as JSON response from API requests.

Following code snippet shows a helper function called by completion handler for HTTP request. This function converts response data into NSDictionary using NSJSONSerialization class.

    func processWeatherDataResponse(response:HttpResponse,
    completionHandler:((OpenWeatherMapData, ApiError?)->Void)?){
        if let err = response.error{
            print("Api Response Error: \(err.description)")
        }
        else{
            // Convert JSON data to WeatherData object and then call
            // completion handler if it has been specified
            if let handler = completionHandler{
                var weatherData = OpenWeatherMapData()
                weatherData.rawJsonData = (NSString(data:response.responseData,
                    encoding: NSUTF8StringEncoding) as! String)
                do{
                    let jsonData = try NSJSONSerialization.JSONObjectWithData(response.responseData,
                        options: NSJSONReadingOptions.AllowFragments)
                    weatherData = OpenWeatherMapData(jsonDictionary: jsonData as! NSDictionary)
                    handler(weatherData, nil)
                }
                catch let error as NSError {
                    print("Api Error: \(error.description)")
                    let apiError = ApiError(status: error.code, message: error.localizedDescription)
                    handler(weatherData, apiError)
                }
            }
        }
    }

One thing you will notice that JSONObjectWithData method call has been enclosed in try/catch error handling block. The signature of this method specifies that the method is capable of throwing exception if JSON string is not well formed or can not be deserialized. NSDictionary object created by JSONObjectWithData method is passed to OpenWeatherMapData initializer. Following code snippet shows complete implementation of OpenWeatherMapData class. Here you will see how class initializer uses NSDictionary to initialize all class properties.

    import Foundation
import CoreLocation
public class OpenWeatherMapData{
    public var rawJsonData:String?
    public var geoLocation:CLLocation?
    public var countryCode:String?
    public var sunriseTimeUtc:NSDate?
    public var sunsetTimeUtc:NSDate?
    public var cityName:String?
    public var cityId:String?
    public var dataUpdateDateUtc:NSDate?
    public var currentTemperature:Double?
    public var maxTemperature:Double?
    public var minTemperature:Double?
    public var humidity:Double?
    public var pressure:Double?
    public var pressureAtSeaLeval:Double?
    public var pressureAtGoundLevel:Double?
    public var windSpeed:Double?
    public var windDirection:Double?
    public var cloudiness:Double?
    public var rainVolumeInLast3h:Double?
    public var snowVolumneInLast3h:Double?
    public var visibility:Double?
    public var weatherConditions:[OpenWeatherConditionData]?
    public init(){}
    public init(jsonDictionary jsonData:NSDictionary){
        if let name = jsonData["name"]{
            self.cityName = name as? String
        }
        if let id = jsonData["id"]{
            self.cityId = String(id)
        }
        if let dt = jsonData["dt"]{
            if let num = dt as? NSNumber{
                self.dataUpdateDateUtc = NSDate(timeIntervalSince1970: num.doubleValue)
            }
        }
        ParseMainData(jsonDictionary: jsonData)
        ParseSysData(jsonDictionary: jsonData)
        ParseLocationData(jsonDictionary: jsonData)
        ParseCloudData(jsonDictionary: jsonData)
        ParseWindData(jsonDictionary: jsonData)
        ParseSnowData(jsonDictionary: jsonData)
        ParseRainData(jsonDictionary: jsonData)
        ParseWeatherConditionData(jsonDictionary: jsonData)
    }

    public var description:String{
        var desc = ""
        if let city = cityName{
            desc += "City: \(city)\n"
        }
        if let country = countryCode{
            desc += "Country: \(country)\n"
        }
        if let sunrise = sunriseTimeUtc{
            desc += "Sunrise time (UTC):\(sunrise)\n"
        }
        if let sunset = sunsetTimeUtc{
            desc += "Sunset time (UTC):\(sunset)\n"
        }
        if let curTemp = currentTemperature{
            desc += "Current Temperature: \(curTemp)\n"
        }
        if let humidity = self.humidity{
            desc += "Humidity:\(humidity)\n"
        }
        if let temp_max = maxTemperature{
            desc += "Maximum temparature: \(temp_max)\n"
        }
        if let temp_min = minTemperature{
            desc += "Minimum temperature: \(temp_min)\n"
        }
        return desc
    }

    private func ParseMainData(jsonDictionary jsonData:NSDictionary){
        if let main = jsonData["main"] as? NSDictionary{
            if let humidity = main["humidity"]{
                self.humidity = (humidity as? NSNumber)?.doubleValue
            }
            if let pressure = main["pressure"]{
                self.pressure = (pressure as? NSNumber)?.doubleValue
            }
            if let temp = main["temp"]{
                self.currentTemperature = (temp as? NSNumber)?.doubleValue
            }
            if let temp_max = main["temp_max"]{
                self.maxTemperature = (temp_max as? NSNumber)?.doubleValue
            }
            if let temp_min = main["temp_min"]{
                self.minTemperature = (temp_min as? NSNumber)?.doubleValue
            }
            if let sea_level = main["sea_level"]{
                self.pressureAtSeaLeval = (sea_level as? NSNumber)?.doubleValue
            }
            if let grnd_level = main["grnd_level"]{
                self.pressureAtGoundLevel = (grnd_level as? NSNumber)?.doubleValue
            }
        }
    }

    private func ParseSysData(jsonDictionary jsonData:NSDictionary){
        if let sys = jsonData["sys"] as? NSDictionary{
            if let country = sys["country"]{
                self.countryCode = country as? String
            }
            if let sunrise = sys["sunrise"]{
                if let num = sunrise as? NSNumber{
                    self.sunriseTimeUtc = NSDate(timeIntervalSince1970: num.doubleValue)
                }
            }
            if let sunset = sys["sunset"]{
                if let num = sunset as? NSNumber{
                    self.sunsetTimeUtc = NSDate(timeIntervalSince1970: num.doubleValue)
                }
            }
        }
    }

    private func ParseVisibilityData(jsonDictionary jsonData:NSDictionary){
        if let vis = jsonData["visibility"] as? NSDictionary{
            if let dist = vis["value"]{
                self.visibility = (dist as? NSNumber)?.doubleValue
            }
        }
    }

    private func ParseCloudData(jsonDictionary jsonData:NSDictionary){
        if let clouds = jsonData["clouds"] as? NSDictionary{
            if let cloudiness = clouds["all"]{
                self.cloudiness = (cloudiness as? NSNumber)?.doubleValue
            }
        }
    }

    private func ParseLocationData(jsonDictionary jsonData:NSDictionary){
        if let coord = jsonData["coord"] as? NSDictionary{
            var latitude:Double = 0
            var longitude:Double = 0
            if let lat = coord["lat"]{
                latitude = ((lat as? NSNumber)?.doubleValue)!
            }
            if let lon = coord["lon"]{
                longitude = ((lon as? NSNumber)?.doubleValue)!
            }
            self.geoLocation = CLLocation(latitude: latitude, longitude: longitude)
        }
    }

    private func ParseWindData(jsonDictionary jsonData:NSDictionary){
        if let wind = jsonData["wind"] as? NSDictionary{
            if let deg = wind["deg"]{
                self.windDirection = (deg as? NSNumber)?.doubleValue
            }
            if let speed = wind["speed"]{
                self.windSpeed = (speed as? NSNumber)?.doubleValue
            }
        }
    }

    private func ParseSnowData(jsonDictionary jsonData:NSDictionary){
        if let snow = jsonData["snow"]{
            if let threehourData = snow["3h"]{
                self.snowVolumneInLast3h = (threehourData as? NSNumber)?.doubleValue
            }
        }
    }

    private func ParseRainData(jsonDictionary jsonData:NSDictionary){
        if let rain = jsonData["rain"]{
            if let threehourData = rain["3h"]{
                self.rainVolumeInLast3h = (threehourData as? NSNumber)?.doubleValue
            }
        }
    }

    private func ParseWeatherConditionData(jsonDictionary jsonData:NSDictionary){
        if let weathers = jsonData["weather"] as? NSArray{
            if (weathers.count > 0){
                self.weatherConditions = [OpenWeatherConditionData]()
                for weather in weathers{
                    var weatherData = OpenWeatherConditionData()
                    if let id = weather["id"]{
                        weatherData.weatherConditionId = (id as? NSNumber)?.integerValue
                    }
                    if let description = weather["description"]{
                        weatherData.weatherConditionDescription = description as? String
                    }
                    if let main = weather["main"]{
                        weatherData.weatherCondition = main as? String
                    }
                    if let icon = weather["icon"]{
                        weatherData.weatherConditionIconId = icon as? String
                    }
                    self.weatherConditions?.append(weatherData)
                }
            }
        }
    }
}
    public struct OpenWeatherConditionData{
        public var weatherConditionId:Int?
        public var weatherConditionIconId:String?
        public var weatherConditionDescription:String?
        public var weatherCondition:String?
    }

Using OpenWeatherMapData

Location service delegate notifies of location updates. Then client calls appropriate method in OpenWeatherMapApiClient class and passes it the location. OpenWeatherMapApiClient makes REST request to OpenWeatherMap and gets weather data for that location. The following code snippet shows how iOS view uses OpenWeatherMapData object to update the view elements.

    private func updateWeatherConditionView(weatherData: OpenWeatherMapData?){
        if let data = weatherData{
            errorLabel.text = ""
            var locationText = ""
            if let city = data.cityName{
                locationText += city
            }
            if let country = data.countryCode{
                locationText += ",\(country)"
            }
            cityStateLabel.text = locationText
            if let currentTemp = data.currentTemperature{
                currentTemperatureLabel.text = "\(NSString(format:"%.2f",
                TemperatureConverter.ToCelcius(kelvin: currentTemp)))℃"
            }
            if let conditions = data.weatherConditions{
                var text = ""
                for condition in conditions{
                    text += "\(condition.weatherCondition!),"
                }
                weatherConditionLabel.text = text
            }
            var minmaxText = "--"
            if let min = data.minTemperature{
                minmaxText = "\(NSString(format:"%.2f",
                    TemperatureConverter.ToCelcius(kelvin:min)))"
            }
            minmaxText += "/"
            if let max = data.maxTemperature{
                minmaxText += "\(NSString(format:"%.2f",
                    TemperatureConverter.ToCelcius(kelvin:max)))"
            }
            else{
                minmaxText += "--"
            }
            minmaxText += "℃"
            minmaxTemperatureLabel.text = minmaxText
            let dateFormatter = NSDateFormatter()
            dateFormatter.dateFormat = "MMM d, HH:mm a"
            if let sunrise = data.sunriseTimeUtc{
                sunriseTimeLabel.text = dateFormatter.stringFromDate(sunrise)
            }
            else{
                sunriseTimeLabel.text = "--:--"
            }

            if let sunset = data.sunsetTimeUtc{
                sunsetTimeLabel.text = dateFormatter.stringFromDate(sunset)
            }
            else{
                sunriseTimeLabel.text = "--:--"
            }

        }else{
            cityStateLabel.text = "---"
            currentTemperatureLabel.text = "---"
            weatherConditionLabel.text = "---"
            minmaxTemperatureLabel.text = "--/--"
            sunsetTimeLabel.text = "--:--"
            sunriseTimeLabel.text = "--:--"
            errorLabel.text = "Location not available!"
        }
    }

comments powered by Disqus

Blog Tags