Victor Victor - 5 months ago 29
Swift Question

Swift flatMap and generics

I'm trying to use flatMap to build a

Resource<T>
in Swift, but keep getting a strange error, and only works when I force the cast.

Resource<T>
:

public struct Resource<T> {
let record: CKRecord
let parser: [String: AnyObject] -> T?
}


Working code:

public func buildResource<T>(resource: Resource<T>) -> T? {
var dataJson: [String: AnyObject] = [:]
dataJson["recordID"] = resource.record.recordID
for name in resource.record.attributeKeys {
dataJson[name] = resource.record[name]
}
return (dataJson as? [String: AnyObject]).flatMap(resource.parser)
}


The code above gives a warning that the casts always succeeds, which is true. But when I try to remove the cast like so:

public func buildResource<T>(resource: Resource<T>) -> T? {
var dataJson: [String: AnyObject] = [:]
dataJson["recordID"] = resource.record.recordID
for name in resource.record.attributeKeys {
dataJson[name] = resource.record[name]
}
return dataJson.flatMap(resource.parser)
}


It gives the following error:
'flatMap' produces '[S.Generator.Element]', not the expected contextual result type 'T'?
.

The parser is a
struct init
like so:

struct Example {

let name: String
let id: Int
}

extension Example {

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

}


Any ideas how to fix this or a different approach? The idea here is to transform any CKRecord into a struct easily without needing to write a lot of boilerplate code.

Answer

Daniel Hall's answer is right but, by doing this, you'll be forced to change your parser init signature to receive (String, AnyObject).

The best option would be to create another init with this signature and parsing it to your json signature's init, still being able to create this struct from a raw json.

extension Example {

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

    init (tuple : (String, AnyObject)) {
        var json : [String : AnyObject] = [:]
        json["name"] = tuple.0
        json["id"] = tuple.1
        self.init(json: json)!
    }
}

EDIT

As you're creating your dataJson as a [String : AnyObject], you don't need to do a flatMap on it, you can just return resource.parser(json: dataJson)