问题
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