如何将Swift JSONDecode与动态类型一起使用? [英] How to use Swift JSONDecode with dynamic types?
问题描述
我的应用程序具有本地缓存,可以从服务器发送模型或从服务器接收模型.因此,我决定构建一个映射[String:Codable.Type],实质上是为了能够解码我在本地创建的通用缓存或从服务器接收的通用缓存上的所有内容.
My App has a local cache and sends/receives models from/to the server. So I decided to build a map [String : Codable.Type], essentially to be able to decode anything I have on this generic cache either created locally or received from server.
let encoder = JSONEncoder()
let decoder = JSONDecoder()
var modelNameToType = [String : Codable.Type]()
modelNameToType = ["ContactModel": ContactModel.Self, "AnythingModel" : AnythingModel.Self, ...]
无论我在App上创建的内容如何,我都可以成功进行编码并将其存储在缓存中,如下所示:
Whatever I create on the App I can encode successfully and store on cache like this:
let contact = ContactModel(name: "John")
let data = try! encoder.encode(contact)
CRUD.shared.storekey(key: "ContactModel$10", contact)
我想这样解码:
let result = try! decoder.decode(modelNameToType["ContactModel"]!, from: data)
但是我得到了错误:
无法使用类型为(Codable.Type, 来自:数据)
Cannot invoke 'decode' with an argument list of type (Codable.Type, from: Data)
我做错了什么?感谢您的帮助
What am I doing wrong? Any help is appreciated
修复类型有效,并且可以解决任何本地请求,但不能解决远程请求.
Fixing the type works, and solves any local request, but not a remote request.
let result = try! decoder.decode(ContactModel.self, from: data)
联系方式:
struct ContactModel: Codable {
var name : String
}
对于远程请求,我将具有以下功能:
For remote requests I would have a function like this:
func buildAnswer(keys: [String]) -> Data {
var result = [String:Codable]()
for key in keys {
let data = CRUD.shared.restoreKey(key: key)
let item = try decoder.decode(modelNameToType[key]!, from: data)
result[key] = item
}
return try encoder.encode(result)
}
...如果我解决了解码问题.任何帮助表示赞赏.
...if I solve the decode issue. Any help appreciated.
推荐答案
Codable
API围绕着具体类型的编码和解码而构建.但是,您在此处想要进行的往返操作不必了解任何具体类型;只是将异构JSON值连接到JSON对象中.
The Codable
API is built around encoding from and decoding into concrete types. However, the round-tripping you want here shouldn't have to know about any concrete types; it's merely concatenating heterogenous JSON values into a JSON object.
因此,在这种情况下,JSONSerialization
是工作的更好工具,因为它处理Any
:
Therefore, JSONSerialization
is a better tool for the job in this case, as it deals with Any
:
import Foundation
// I would consider lifting your String keys into their own type btw.
func buildAnswer(keys: [String]) throws -> Data {
var result = [String: Any](minimumCapacity: keys.count)
for key in keys {
let data = CRUD.shared.restoreKey(key: key)
result[key] = try JSONSerialization.jsonObject(with: data)
}
return try JSONSerialization.data(withJSONObject: result)
}
话虽这么说,您仍然可以用JSONDecoder
/JSONEncoder
做到这一点–但是它需要大量的类型擦除样板.
That being said, you could still make this with JSONDecoder
/JSONEncoder
– however it requires quite a bit of type-erasing boilerplate.
例如,我们需要一个符合Encodable
的包装类型,因为Encodable
不符合其自身:
For example, we need a wrapper type that conforms to Encodable
, as Encodable
doesn't conform to itself:
import Foundation
struct AnyCodable : Encodable {
private let _encode: (Encoder) throws -> Void
let base: Codable
let codableType: AnyCodableType
init<Base : Codable>(_ base: Base) {
self.base = base
self._encode = {
var container = $0.singleValueContainer()
try container.encode(base)
}
self.codableType = AnyCodableType(type(of: base))
}
func encode(to encoder: Encoder) throws {
try _encode(encoder)
}
}
我们还需要一个包装器来捕获可用于解码的具体类型:
We also need a wrapper to capture a concrete type that can be used for decoding:
struct AnyCodableType {
private let _decodeJSON: (JSONDecoder, Data) throws -> AnyCodable
// repeat for other decoders...
// (unfortunately I don't believe there's an easy way to make this generic)
//
let base: Codable.Type
init<Base : Codable>(_ base: Base.Type) {
self.base = base
self._decodeJSON = { decoder, data in
AnyCodable(try decoder.decode(base, from: data))
}
}
func decode(from decoder: JSONDecoder, data: Data) throws -> AnyCodable {
return try _decodeJSON(decoder, data)
}
}
我们不能简单地将Decodable.Type
传递给JSONDecoder
的
We cannot simply pass a Decodable.Type
to JSONDecoder
's
func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T
就像T
是协议类型一样,type:
参数采用.Protocol
元类型,而不是.Type
元类型(请参阅
as when T
is a protocol type, the type:
parameter takes a .Protocol
metatype, not a .Type
metatype (see this Q&A for more info).
我们现在可以使用modelType
属性定义键的类型,该属性返回用于解码JSON的AnyCodableType
:
We can now define a type for our keys, with a modelType
property that returns an AnyCodableType
that we can use for decoding JSON:
enum ModelName : String {
case contactModel = "ContactModel"
case anythingModel = "AnythingModel"
var modelType: AnyCodableType {
switch self {
case .contactModel:
return AnyCodableType(ContactModel.self)
case .anythingModel:
return AnyCodableType(AnythingModel.self)
}
}
}
,然后为往返执行以下操作:
and then do something like this for the round-tripping:
func buildAnswer(keys: [ModelName]) throws -> Data {
let decoder = JSONDecoder()
let encoder = JSONEncoder()
var result = [String: AnyCodable](minimumCapacity: keys.count)
for key in keys {
let rawValue = key.rawValue
let data = CRUD.shared.restoreKey(key: rawValue)
result[rawValue] = try key.modelType.decode(from: decoder, data: data)
}
return try encoder.encode(result)
}
这可能更适合与 Codable
一起使用,而不是与之抗衡(也许是一种结构,代表发送到服务器的JSON对象,并使用关键路径与缓存进行交互)层),但不了解有关CRUD.shared
以及如何使用它的更多信息;很难说.
This probably could be designed better to work with Codable
rather than against it (perhaps a struct to represent the JSON object you send to the server, and use key paths to interact with the caching layer), but without knowing more about CRUD.shared
and how you use it; it's hard to say.
这篇关于如何将Swift JSONDecode与动态类型一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!