Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 499
Victor Avatar asked Mar 13 '23 01:03

Victor


2 Answers

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)

like image 181
haroldolivieri Avatar answered Mar 21 '23 00:03

haroldolivieri


Looks like you have the wrong signature for your parser function. The entire json dictionary is of type [String : AnyObject], but the individual elements in that dictionary when enumerated with flatMap() are of type (String, AnyObject), not [String : AnyObject].

Try changing this:

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

to this:

public struct Resource<T> {
    let record: CKRecord
    let parser: (String, AnyObject) -> T?
}
like image 23
Daniel Hall Avatar answered Mar 20 '23 22:03

Daniel Hall