将编码器存储在 Swift 字典中
Store Encodables in a Swift Dictionary
我希望将模型对象存储在字典中,并希望使用 JSONEncoder
将整个字典序列化为数据,然后序列化为字符串并保存。
我的想法是使用 Swift 开箱即用的 4 Encodable
来确保我添加到字典中的任何内容都将被序列化,其中可以包括基元和自定义对象(它们本身符合Encodable
)。
挑战是我应该将字典声明为什么类型:
- 如果我使用
[String: Any]
,它不会知道如何编码 Any
,如果我必须将它转换为实际的具体类型,这有点违背了泛型的目的
- 如果我使用
[String: Encodable]
,它会在 运行 时崩溃说
Encodable 不符合自身,这是可以理解的,因为它
需要具体类型
为了解决这个问题,我想到了创建一个包装器:
即具有关联类型的协议 或 具有通用类型值的结构:
struct Serializable<T: Encodable> {
var value: T?
init(value: T) {
self.value = value
}
}
但问题依然存在,在声明上述字典的类型时,我仍然必须提供具体类型..
var dictionary: [String: Serializable<X>]
这里 'X' 应该是什么,或者,实现它的正确方法是什么?
我错过了什么?
两种可能的方法:
您可以创建字典,其值是 Encodable
简单编码基础值的包装器类型:
struct EncodableValue: Encodable {
let value: Encodable
func encode(to encoder: Encoder) throws {
try value.encode(to: encoder)
}
}
那么你可以这样做:
let dictionary = [
"foo": EncodableValue(value: Foo(string: "Hello, world!")),
"bar": EncodableValue(value: Bar(value: 42)),
"baz": EncodableValue(value: "qux")
]
let data = try! JSONEncoder().encode(dictionary)
您可以定义自己的 Codable
类型而不是使用字典:
struct RequestObject: Encodable {
let foo: Foo
let bar: Bar
let baz: String
}
let requestObject = RequestObject(
foo: Foo(string: "Hello, world!"),
bar: Bar(value: 42),
baz: "qux"
)
let data = try! JSONEncoder().encode(requestObject)
不用说了,这些都假设Foo
和Bar
都符合Encodable
。
这是我的解决方案(由 Rob 回答改进):
struct EncodableValue: Encodable {
let value: Encodable
func encode(to encoder: Encoder) throws {
try value.encode(to: encoder)
}
}
struct Storage: Encodable {
var dict: [String: Encodable] = [:]
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in dict {
guard let codingKey = CodingKeys(stringValue: key) else {
continue
}
if let enc = value as? EncodableValue {
try container.encode(enc, forKey: codingKey)
}
}
}
struct CodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
}
let dict: [String: EncodableValue] = ["test": EncodableValue(value:1), "abc":EncodableValue(value:"GOGO")]
let storage = Storage(dict: dict)
do {
let data = try JSONEncoder().encode(storage)
let res = String(data: data, encoding: .utf8)
print(res ?? "nil")
} catch {
print(error)
}
我希望将模型对象存储在字典中,并希望使用 JSONEncoder
将整个字典序列化为数据,然后序列化为字符串并保存。
我的想法是使用 Swift 开箱即用的 4 Encodable
来确保我添加到字典中的任何内容都将被序列化,其中可以包括基元和自定义对象(它们本身符合Encodable
)。
挑战是我应该将字典声明为什么类型:
- 如果我使用
[String: Any]
,它不会知道如何编码Any
,如果我必须将它转换为实际的具体类型,这有点违背了泛型的目的 - 如果我使用
[String: Encodable]
,它会在 运行 时崩溃说 Encodable 不符合自身,这是可以理解的,因为它 需要具体类型
为了解决这个问题,我想到了创建一个包装器: 即具有关联类型的协议 或 具有通用类型值的结构:
struct Serializable<T: Encodable> {
var value: T?
init(value: T) {
self.value = value
}
}
但问题依然存在,在声明上述字典的类型时,我仍然必须提供具体类型..
var dictionary: [String: Serializable<X>]
这里 'X' 应该是什么,或者,实现它的正确方法是什么? 我错过了什么?
两种可能的方法:
您可以创建字典,其值是
Encodable
简单编码基础值的包装器类型:struct EncodableValue: Encodable { let value: Encodable func encode(to encoder: Encoder) throws { try value.encode(to: encoder) } }
那么你可以这样做:
let dictionary = [ "foo": EncodableValue(value: Foo(string: "Hello, world!")), "bar": EncodableValue(value: Bar(value: 42)), "baz": EncodableValue(value: "qux") ] let data = try! JSONEncoder().encode(dictionary)
您可以定义自己的
Codable
类型而不是使用字典:struct RequestObject: Encodable { let foo: Foo let bar: Bar let baz: String } let requestObject = RequestObject( foo: Foo(string: "Hello, world!"), bar: Bar(value: 42), baz: "qux" ) let data = try! JSONEncoder().encode(requestObject)
不用说了,这些都假设Foo
和Bar
都符合Encodable
。
这是我的解决方案(由 Rob 回答改进):
struct EncodableValue: Encodable {
let value: Encodable
func encode(to encoder: Encoder) throws {
try value.encode(to: encoder)
}
}
struct Storage: Encodable {
var dict: [String: Encodable] = [:]
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in dict {
guard let codingKey = CodingKeys(stringValue: key) else {
continue
}
if let enc = value as? EncodableValue {
try container.encode(enc, forKey: codingKey)
}
}
}
struct CodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
}
let dict: [String: EncodableValue] = ["test": EncodableValue(value:1), "abc":EncodableValue(value:"GOGO")]
let storage = Storage(dict: dict)
do {
let data = try JSONEncoder().encode(storage)
let res = String(data: data, encoding: .utf8)
print(res ?? "nil")
} catch {
print(error)
}