14

I'm writing an extension to bridge the dictionary values between FirebaseDatabase and Eureka.

private extension Dictionary {
    func firebaseFriendlyDictionary() -> [String: Any?] {
        return self.map({ (key: String, value: Any?) -> (String, Any?) in
            if value is NSDate {
                return (key, (value as! NSDate).timeIntervalSince1970)
            }
            return (key, value)
        })
    }
}

But I get thrown this error when I try to build:

map produces '[T]', not the expected contextual result type '[String: Any?]'

Liau Jian Jie
  • 7,071
  • 2
  • 15
  • 16

3 Answers3

7

Your problem lies with the fact, that map always returns an Array, even when applied on a Dictionary. Your error message basically means, that you declared your method as returning a Dicitonary, but the statement inside returns an Array ([T] - means an Array with objects of some type T). In your case, the array returned by map will contain tuples (more about them here). In this case it looks like a key value pair, but its not equivalent to a key-value pair inside a Dictionary. Basically, tuples enable you to return more than one value/object from method. You can think of them as of anonymous structures.

In my opinion, there is no need to use a map to accomplish what you need - the solution provided by Mr. Xcoder is the way to go.

Mr. Xcoder
  • 4,719
  • 5
  • 26
  • 44
Losiowaty
  • 7,911
  • 2
  • 32
  • 47
  • 2
    Marked as correct answer for clarity in explanation. In short: array of tuples ≠ dictionary... `[(String, Any?)] ≠ [String: Any?]`. Thank you! – Liau Jian Jie Feb 04 '17 at 18:01
6

If you really want to use something functional like map, the method you actually want is reduce.

I'll demonstrate. To make things as clear as possible, I think it will help if we separate out the transformation to which your values are being subjected into a function of its own:

func dateToSeconds(_ thing:Any?) -> Any? {
    guard let date = thing as? Date else {return thing}
    return date.timeIntervalSince1970
}

Okay, so here's our test dictionary:

let d1 : [String:Any?] = ["1":Date(), "2":"two", "3":15, "4":true]

Now we're ready to apply reduce. It passes two parameters into its function. The first is the "accumulator" where we keep building up the ultimate result, which in this case is another dictionary. The second is an element of the original dictionary, represented as a tuple of key-value pairs called key and value:

let d2 = d1.reduce([String:Any?]()) { (dict, tuple) in
    var dict = dict
    dict[tuple.key] = dateToSeconds(tuple.value)
    return dict
}

And when we examine d2, we see that we have got the right answer:

d2 // ["3": {some 15}, "2": {some "two"}, "1": {some 1486228695.557882}, "4": {some true}]
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Reduce seems like the way to go. I've marked [Losiowaty's answer](http://stackoverflow.com/a/42042946/4773291) as the answer instead because it explains the error. Thank you! – Liau Jian Jie Feb 04 '17 at 18:04
  • 2
    I don't know what you are doing, but what I showed you does work. Note that in the first line of my `reduce` function I assign `dict` into a `var`; is that what you are talking about? That line is crucial. (Well, it's all crucial; it all hangs together.) – matt Feb 04 '17 at 19:04
  • Oh yes that was it. I had no idea it mattered, I did not see that in the docs. Thanks, again! – Liau Jian Jie Feb 04 '17 at 19:15
3

I couldn't figure out how to fix that error, nor have I succeeded achieving the desired result with an extension or a map(), but I have an alternative solution to the problem, using a function:

Declaring Dictionary:

var date = NSDate()
var swiftDict:[String : Any?] = ["1":  date, "2": "two", "3": 15, "4": true]

Function:

func firebaseFriendlyDictionary(_ dict: [String : Any?]) -> [String : Any?]{
    var Dict = dict
    for (key, value) in dict
    {
        if (value is NSDate){
            Dict[key] = (value as! NSDate).timeIntervalSince1970
        }
    }
    return Dict
}

Usage:

swiftDict = firebaseFriendlyDictionary(swiftDict)

Testing:

Assuming that we have the date 2017-02-04 16:42:46 +0000 the output is 1486226566.2349629, which is correct.

Why not mapping the Dictionary? As Losiowaty pointed in his excellent answer, map always returns an Array, in this case an Array of Tuples([T]). In my opinion a map function is not needed in this context, plus it requires more code to accomplish.

Hope this helps!

Mr. Xcoder
  • 4,719
  • 5
  • 26
  • 44