Swift Decode [String: Any]

主宰稳场 提交于 2020-08-03 06:01:09

问题


So I have this API that returns a dictionary of [String: Any], I know that what comes as Any is Decodable or an array of Decodable however I can't for the life of me figure out how to take that dictionary and decode it to some struct:

What I have goes basically like this:

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)
    })
}

I've tried turning it to data and then decoding it with:

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

but it always fails with NSInvalidArgumentException, any idea how to get around this?

Another thing that I was trying to achieve but failed to do so is to turn values into a tuple, but for all I could find it's not possible to create tuples dynamically.


回答1:


Decoders convert Data into Decodable values. They don't have anything to do with [String: Any] types or any other non-Data type. So if you want to run it through a decoder, you need to convert it to JSON encoded into Data.

If the [String: Any] results are exclusively JSONSerialization-safe types (arrays, dictionaries, strings, numbers, null), then JSONSerialization.data(withJSONObject:options:) would let you get back to data, so you can re-decode it. Your code doesn't just re-encode its results, it first turns it into an array:

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

That's very strange. Do you really mean to create an array here and throw away the keys? I would then expect your JSONDecoder().decode() line to decode [T].self rather than T.self. So I would expect the following code (provided that your [String: Any] is JSON-safe):

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)
    }
}

In the comments you note that the decoded data (the [String: Any]) is not made of primitives. In that case it's not possible to re-encode it with JSONSerialization. You'll need to pass the [String: Any] to something that knows how to deal with it. For example:

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))
    }
}

Your types will need to implement an init?(dictionary:) that can decode their own values out of a [String: Any].



来源:https://stackoverflow.com/questions/55047138/swift-decode-string-any

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!