Быстрое декодирование [Строка: Любая] - PullRequest
1 голос
/ 07 марта 2019

Итак, у меня есть этот API, который возвращает словарь [String: Any], я знаю, что значение Any равно Decodable или массиву Decodable, однако я не могу на всю жизнь сообразить, как взять этот словарь и декодировать его в какую-то структуру:

То, что я имею, выглядит в основном так:

public func call<T: Codable> (completion handler: @escaping (T?) -> ()) {
    let promise = api.getPromise ()
    promise.done (on: DispatchQueue.main, { (results: [String:Any])
        let decodedResults:T? = results.decode (as: T.self) // <-- this is what I want
        handler (decodedResults)
    })
}

Я попытался преобразовать его в данные и затем расшифровать его с помощью:

let values = results.compactMap { $0.value }
let data = JSONSerialization.data (withJSONObject: values, options: [])
let decodedResult = JSONDecoder().decode(T.self, from: data)

но это всегда терпит неудачу с NSInvalidArgumentException, есть идеи, как обойти это?

Еще одна вещь, которую я пытался достичь, но не смог сделать, - это превратить значения в кортеж, но, как я понял, невозможно динамически создавать кортежи.

1 Ответ

2 голосов
/ 07 марта 2019

Декодеры преобразуют данные в декодируемые значения.Они не имеют ничего общего с типами [String: Any] или другими типами, не относящимися к данным.Поэтому, если вы хотите запустить его через декодер, вам нужно преобразовать его в JSON, закодированный в Data.

Если результаты [String: Any] являются исключительно безопасными для JSONSerialization типами (массивы, словари, строки, числа, нуль)), тогда JSONSerialization.data(withJSONObject:options:) позволит вам вернуться к данным, чтобы вы могли повторно декодировать их.Ваш код не просто перекодирует свои результаты, он сначала превращает его в массив:

let values = results.compactMap { $0.value }
let data = JSONSerialization.data (withJSONObject: values, options: [])

Это очень странно.Вы действительно хотите создать массив и выбросить ключи?Тогда я ожидал бы, что ваша строка JSONDecoder().decode() будет декодировать [T].self, а не T.self.Поэтому я ожидаю следующий код (при условии, что ваш [String: Any] является JSON-безопасным):

public func call<T: Decodable>(completion handler: @escaping (T?) -> ()) {
    let promise = api.getPromise()
    promise.done(on: .main) { (results: [String:Any]) in
        guard JSONSerialization.isValidJSONObject(results) else {
            handler(nil)
            return
        }

        let data = JSONSerialization.data(withJSONObject: results)
        let decodedResults = try? JSONDecoder().decode(T.self, from: data)
        handler(decodedResults)
    }
}

В комментариях вы заметите, что декодированные данные ([String: Any]) не состоят из примитивов.В этом случае невозможно перекодировать его с помощью JSONSerialization.Вам нужно будет передать [String: Any] кому-то, кто знает, как с этим справиться.Например:

protocol DictionaryDecodable {
    init?(dictionary: [String: Any])
}

public func call<T: DictionaryDecodable>(completion handler: @escaping (T?) -> ()) {
    let promise = api.getPromise ()
    promise.done(on: .main) { (results: [String:Any])
        handler(T.init(dictionary: results))
    }
}

Вашим типам потребуется реализовать init?(dictionary:), который может декодировать свои собственные значения из [String: Any].

...