4

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.

Victor
  • 137
  • 1
  • 11

2 Answers2

4

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)

haroldolivieri
  • 2,173
  • 18
  • 29
3

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?
}
Daniel Hall
  • 13,457
  • 4
  • 41
  • 37