WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: JSON Decodable, objects and arrays?!

Forums > Swift

Hi everyone, first post for me here..

I am learning hacking around the OpenWeatherAPI and I have managed to use structs with the Decodable protocol to parse simple values from a OpenWeatherAPI call. For example I can access the longitude and latitude inside 'coord'

The next part (weather) is contained inside an array and I can't understand how to organise that in my structs.. This is what I have tried, with the source JSON attached below.

Any idea? Thanks!!

JSON

{
   "coord":{
      "lon":0.12,
      "lat":51.5
   },
   "weather":[
      {
         "id":800,
         "main":"Clear",
         "description":"clear sky",
         "icon":"01d"
      }
   ],
   "timezone":3600,
   "id":7302135,
   "name":"Abbey Wood",
   "cod":200
}

MY STRUCTS

struct WeatherAPI: Decodable {

    let name: String
    var coord: CoordDetail
    var weather: [Weather]
}

struct CoordDetail: Decodable {

    let lat: Double
    let lon: Double

    private enum CodingKeys: String, CodingKey {
        case lat = "lat"
        case lon = "lon"
    }
}

struct Weather: Decodable {
    let id: Int
    var main: String
    var weatherDescription: String
    var icon: String

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case main = "main"
        case weatherDescription = "description"
        case icon
    }
}

INSIDE THE VIEW


//this works fine
                            if let latitude = response.value?.coord.lat {
                                self.latValue = latitude
                                print("lat \(latitude)")
                            }

                            if let longitude = response.value?.coord.lon {
                                self.lonValue = longitude
                                print("lon \(longitude)")
                            }

//this doesn't work.. how can I access the array? 

                            if let main = response.value?.weather.description {
                                self.mainValue = main
                                print(main)
                            }

I can see this printed in the console, but how can I access each single element (e.g. main and weatherDescription?) [WebService.Weather(id: 802, main: "Clouds", weatherDescription: "scattered clouds", icon: "03d")]

   

Since it's an array, you have to access it by index. The first item is at index 0, the second is at index 1, etc.

So:

response.value?.weather[0].description

1      

Thanks @roosterboy. I did try something similar but somehow I missed this syntax! Works fine now.

Still feels like a very complicated way of going about this. Would you advise about using something different? (the rest of the app uses alamofire at the moment).

Thanks!

   

Well, it really depends on how you are using the data. Here's one way to do it that flattens and simplifies the JSON into a Swift struct:

struct Weather: Decodable {

    let name: String
    let coordinate: CLLocationCoordinate2D
    let id: Int
    let main: String //this should have a better name but I don't know the API well enough
    let weatherDescription: String
    let icon: String

    //printable latitude and longitude
    var latLong: String {
        "\(coordinate.latitude), \(coordinate.longitude)"
    }

    //these are the keys we want to extract from the JSON
    private enum CodingKeys: String, CodingKey {
        case name, coord, weather
    }

    //used for keying the coord object
    private enum CoordinateKeys: String, CodingKey {
        case latitude = "lat"
        case longitude = "lon"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        //name is easy; that's just a String
        name = try container.decode(String.self, forKey: .name)

        //get the coordinate object from the JSON
        let coord = try container.nestedContainer(keyedBy: CoordinateKeys.self, forKey: .coord)
        //extract latitude and longitude
        //CLLocationDegrees is a typealias for Double
        let lat = try coord.decode(CLLocationDegrees.self, forKey: .latitude)
        let lon = try coord.decode(CLLocationDegrees.self, forKey: .longitude)
        //and use them to create a CLLocationCoordinate2D
        coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)

        //get a Detail object
        let details = try container.decode([Detail].self, forKey: .weather)
        //extract the fields we want from the first detail item
        //and assign them to our properties
        id = details[0].id
        main = details[0].main
        weatherDescription = details[0].description
        icon = details[0].icon
    }

    //keep this private since we extract the info we need
    //when we decode the JSON
    private struct Detail: Decodable {
        let id: Int
        let main: String
        let description: String
        let icon: String
    }
    //strictly speaking, we don't need this struct and could do the decoding in a
    //similar fashion to how we handled coord, but because weather is an array in the JSON
    //it's a little trickier and it's just easier to do it this way with a private struct
    //that conforms to Decodable
}

Stripped of all the other code, it essentially takes this JSON:

{
   "coord":{
      "lon":0.12,
      "lat":51.5
   },
   "weather":[
      {
         "id":800,
         "main":"Clear",
         "description":"clear sky",
         "icon":"01d"
      }
   ],
   "timezone":3600,
   "id":7302135,
   "name":"Abbey Wood",
   "cod":200
}

and turns it into this Swift struct:

struct WeatherAPI: Decodable {
    let name: String
    let coordinate: CLLocationCoordinate2D
    let id: Int
    let main: String
    let weatherDescription: String
    let icon: String
}

There are other ways this could be done, and if you are using the data differently—say, if you care about more than one entry in the weather array in the JSON—this wouldn't necessarily work for you.

   

Hacking with Swift is sponsored by Emerge

SPONSORED Optimize your app’s startup time, binary size, and overall performance using Emerge’s advanced app optimization and monitoring tools. Reliably measure app size, speed up your app's startup time with Emerge's Launch Booster, and much more. Emerge is actively used by many of the top mobile development teams in the world.

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.