timpone timpone - 5 months ago 45
Swift Question

possible to cast this Alamofire result to an array of dictionaries

I am not an iOS dev and have to make a few changes to a Swift / AlamoFire project (not mine) and am a bit lost.

I have the following JSON:

{"metro_locations":
[
{
"name":"Ruby Red"
},
{
"name":"Blue Ocean"
}
]
}


class (I know that there are issues here):

class Location{
var name=""
init(obj:tmp){
self.name=tmp["name"]
}
}


and need to make an AlamoFire call

Alamofire.request(.GET, "https://www.domain.com/arc/v1/api/metro_areas/1", parameters: nil)
.responseJSON { response in

if let dataFromNetworking = response.result.value {
let metroLocations = dataFromNetworking["metro_locations"]
var locations: [Location]=[]
for tmp in metroLocations as! [Dictionary] { // <- not working, Generic Paramter 'Key' could not be inferred
let location=Location.init(obj: tmp)
locations.append(location)
}
}
}


I have included the error msg, the "not working" but feel that there are issues in other parts too (like expecting a dictionary in the initialization). What does the 'Key' could not be inferred mean and are there other changes I need to make?

edit #1



I have updated my Location to this to reflect your suggestion:

init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] else { return nil }
guard let name = dictionary["name"] else { return nil }
guard let latitude = dictionary["latitude"] else { return nil }
guard let longitude = dictionary["longitude"] else { return nil }
self.name = name as! String
self.id = id as! Int
self.latitude = latitude as! Double
self.longitude = longitude as! Double
}


but I get the error:

Could not cast value of type 'NSNull' (0x10f387600) to 'NSNumber' (0x10f77f2a0).


like this:

enter image description here

I would think that the
guard
statement would prevent this. What am I missing?

Rob Rob
Answer

You can cast metroLocations as an array of dictionaries, namely:

Array<Dictionary<String, String>>

Or, more concisely:

[[String: String]]

Thus:

if let dataFromNetworking = response.result.value {
    guard let metroLocations = dataFromNetworking["metro_locations"] as? [[String: String]] else {
        print("this was not an array of dictionaries where the values were all strings")
        return
    }

    var locations = [Location]()
    for dictionary in metroLocations {
        if let location = Location(dictionary: dictionary) {
            locations.append(location)
        }
    }
}

Where

class Location {
    let name: String

    init?(dictionary: [String: String]) {
        guard let name = dictionary["name"] else { return nil }
        self.name = name
    }
}

Clearly, I used [[String: String]] to represent an array of dictionaries where the values were all strings, as in your example. If the values included objects other than strings (numbers, booleans, etc.), then you might use [[String: AnyObject]].


In your revision, you show us a more complete Location implementation. You should avoid as! forced casting, and instead us as? in the guard statements:

class Location {
    let id: Int
    let name: String
    let latitude: Double
    let longitude: Double

    init?(dictionary: [String: AnyObject]) {
        guard let id = dictionary["id"] as? Int,
            let name = dictionary["name"] as? String,
            let latitude = dictionary["latitude"] as? Double,
            let longitude = dictionary["longitude"] as? Double else {
                return nil
        }
        self.name = name
        self.id = id
        self.latitude = latitude
        self.longitude = longitude
    }
}