Array.map() создает '[T]', а не ожидаемый тип контекстного результата '[String: Any?]'

Я пишу расширение, чтобы соединить значения словаря между FirebaseDatabase и 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)
        })
    }
}

Но я получаю эту ошибку, когда пытаюсь построить:

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

Ответ 1

Ваша проблема заключается в том, что map всегда возвращает Array, даже если применяется к Dictionary. Ваше сообщение об ошибке в основном означает, что вы объявили свой метод как возвращающий Dicitonary, но оператор внутри возвращает a Array ([T]- означает Array с объектами некоторого типа T). В вашем случае массив, возвращаемый map, будет содержать кортежи (подробнее о них здесь). В этом случае он выглядит как пара значений ключа, но не эквивалентен паре ключ-значение внутри Словаря. В основном, кортежи позволяют вам возвращать более одного значения/объекта из метода. Вы можете думать о них как о анонимных структурах.

По моему мнению, нет необходимости использовать map для выполнения того, что вам нужно - решение, предоставленное г-ном Xcoder, - это путь.

Ответ 2

Если вы действительно хотите использовать что-то функциональное, например map, вам действительно нужен метод reduce.

Я продемонстрирую. Чтобы сделать все как можно более ясным, я думаю, что это поможет, если мы отделим трансформацию, которой подвергаются ваши ценности, в свою собственную функцию:

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

Хорошо, так вот наш тестовый словарь:

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

Теперь мы готовы применить reduce. Он передает два параметра в свою функцию. Первый - это "аккумулятор", в котором мы продолжаем формировать конечный результат, который в этом случае является другим словарем. Второй - это элемент исходного словаря, представленный как набор пар ключ-значение, называемый key и value:

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

И когда мы исследуем d2, мы видим, что мы получили правильный ответ:

d2 // ["3": {some 15}, "2": {some "two"}, "1": {some 1486228695.557882}, "4": {some true}]

Ответ 3

Я не мог понять, как исправить эту ошибку, и мне не удалось достичь желаемого результата с расширением или map(), но у меня есть альтернативное решение проблемы, используя функцию:

Объявление словаря:

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

Функция:

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
}

Использование:

swiftDict = firebaseFriendlyDictionary(swiftDict)

Тестирование:

Предполагая, что мы имеем дату 2017-02-04 16:42:46 +0000, результат равен 1486226566.2349629, что верно.

Почему не сопоставление Словаря? Как показал Лозиоватый в своем превосходном ответе, map всегда возвращает Array, в этом случае Array Кортежи ([T]). На мой взгляд, в этом контексте функция карты не нужна, плюс она требует большего количества кода.

Надеюсь, это поможет!