简体   繁体   中英

Accessing embedded JSON using decodable in Swift 4

I am trying to access a particular an embedded array of dictionaries to create my swift objects. I am unsure about how to access that array in the JSON dictionary.

Here is the definition of my Swift object = StarWarsPeople

class StarWarsPeople: Decodable {

    var name: String?
    var height: String?
    var weight: String?
    var hair_color: String?
    var skin_color: String?
    var eye_color: String?
    var birth_year: String?
    var gender: String?
}

Here is my APIClient class:

class StarWarsPeopleAPIClient
{
    class func getStarWarsPeopleInformation (page: Int, completion:@escaping ([StarWarsPeople])-> ()) throws {

        let starWarsPeopleURL = "https://swapi.co/api/people/?page=\(page)"

        let convertedStarWarsPeopleURL = URL(string: starWarsPeopleURL)

        guard let unwrappedConvertedStarWarsPeopleURL = convertedStarWarsPeopleURL else { print("unwrappedConvertedStarWarsPeopleURL did not unwrap"); return}

        let request = URLRequest(url: unwrappedConvertedStarWarsPeopleURL)

        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

            guard let unwrappedData = data else { print("unwrappedData did not unwrap"); return}

            do {
                let starWarsPeopleDataArray = try JSONDecoder().decode([StarWarsPeople].self, from: unwrappedData)

                completion(starWarsPeopleDataArray)
            }
            catch let error {
                print("Error occured here: \(error.localizedDescription)")
            }
        }
        task.resume()
    }

}

Here is my Json, it is the results array that I would like to access, it is an array of dictionaries, which I need to iterate over to create my StarWarsPeople Object.

{
    "count": 87,
    "next": "url",
    "previous": null,
    "results": [
        {
            "name": "Luke Skywalker",
            "height": "172",
            "mass": "77",
            "hair_color": "blond",
            "skin_color": "fair",
            "eye_color": "blue",
            "birth_year": "19BBY",
            "gender": "male",
            "homeworld": "url",
            "films": [
                "url",
                "url",
                "url",
                "url",
                "url"
            ],
            "species": [
                "url"
            ],
            "vehicles": [
                "url",
                "url"

Please read the JSON. You are ignoring the enclosing object

struct Root: Decodable {
    let count: Int
    let next: URL?
    let previous: URL?
    let results : [StarWarsPeople]
}


struct StarWarsPeople: Decodable {

    private enum CodingKeys: String, CodingKey { 
        case name, height, mass
        case hairColor = "hair_color", skinColor = "skin_color"
        case eyeColor = "eye_color", birthYear = "birth_year", gender
    }

    let name: String
    let height: String
    let mass: String
    let hairColor: String
    let skinColor: String
    let eyeColor: String
    let birthYear: String
    let gender: String
}

...
let root = try JSONDecoder().decode(Root.self, from: unwrappedData)
let starWarsPeopleDataArray = root.results
...

Notes:

  • A struct is sufficient.
  • Map the snake_cased keys to camelCased properties.
  • In almost all cases the properties can be declared as constants ( let ).
  • Don't declare all properties schematically as optional. Declare only those as optional which corresponding key can be missing or the value can be null .

Simply define a wrapper struct that holds the results property from the JSON response.

struct ApiResponse: Decodable {
  results: [StarWarsPeople]
}

and later use

let apiResponse = try JSONDecoder().decode(ApiResponse.self, from: unwrappedData)
let starWarsPeopleDataArray = apiResponse.results

to parse it.

Results you are trying to fetch is actually present in results keys. Also we need to use properties same as parameter name( we can use CodingKeys enum too for overriding this).

So, first parse outer JSON , In new struct say StarWarsPeopleParent

class StarWarsPeopleParent: Decodable {
    var count: Int?
    var results: [StarWarsPeople]?
}

Update your StarWarsPeople struct's properties as:

class StarWarsPeople: Decodable {

    var name: String?
    var height: String?
    var mass: String?
    var hair_color: String?
    var skin_color: String?
    var eye_color: String?
    var birth_year: String?
    var gender: String?
}

Then parse it like:

    let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

        guard let unwrappedData = data else { print("unwrappedData did not unwrap"); return}

        do {
            let starWarsPeopleDataArray = try JSONDecoder().decode(StarWarsPeopleParent.self, from: unwrappedData)

            completion(starWarsPeopleDataArray)
        }
        catch let error {
            print("Error occured here: \(error.localizedDescription)")
        }
    }

Hope this is fine for you to understand.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM