在struct中,我正在寻找一种具有泛型属性的方法,其中在运行时定义类型如下:
struct Dog {
let id: String
let value: ??
}在构建json对象时,这是一个有用的简单用例。node可以是int、string、bool、数组等,但除了可以更改的类型之外,对象node保持不变。
在使用protocols (得到了通常的protocol 'X' can only be used as a generic constraint because it has Self or associated type requirements错误)之后,我想出了两个不同的解决方案,#0使用type erasure,#1使用type-erasure和generics。
#0 (类型擦除)
struct AnyDog: Encodable {
enum ValueType: Encodable {
case int(Int)
case string(String)
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}
let id: String
let value: ValueType
init(_ dog: DogString) {
self.id = dog.id
self.value = .string(dog.value)
}
init(_ dog: DogInt) {
self.id = dog.id
self.value = .int(dog.value)
}
}
struct DogString: Encodable{
let id: String
let value: String
var toAny: AnyDog {
return AnyDog(self)
}
}
struct DogInt: Encodable {
let id: String
let value: Int
var toAny: AnyDog {
return AnyDog(self)
}
}
let dogs: [AnyDog] = [
DogString(id: "123", value: "pop").toAny,
DogInt(id: "123", value: 123).toAny,
]
do {
let data = try JSONEncoder().encode(dogs)
print(String(data: data, encoding: .utf8)!)
} catch {
print(error)
} #1 (类型擦除+泛型)
struct AnyDog: Encodable {
enum ValueType: Encodable {
case int(Int)
case string(String)
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}
let id: String
let value: ValueType
}
struct Dog<T: Encodable>: Encodable{
let id: String
let value: T
var toAny: AnyDog {
switch T.self {
case is String.Type:
return AnyDog(id: id, value: .string(value as! String))
case is Int.Type:
return AnyDog(id: id, value: .int(value as! Int))
default:
preconditionFailure("Invalid Type")
}
}
}
let dogs: [AnyDog] = [
Dog<String>(id: "123", value: "pop").toAny ,
Dog<Int>(id: "123", value: 123).toAny,
]
do {
let data = try JSONEncoder().encode(dogs)
print(String(data: data, encoding: .utf8)!)
} catch {
print(error)
}这两种方法都产生了适当的结果:
[{"id":"123","value":"pop"},{"id":"123","value":123}]即使结果是相同的,我坚信,如果考虑到更多的类型,那么方法#1就是更多的scalable,但是对于添加的每个类型,仍然需要在两个不同的区域进行更改。
我相信有一个更好的方法来实现这一点,但未能找到它。会很高兴听到任何关于它的想法或建议。
编辑#0 2020/02/08:可选值
使用Rob的好答案,我现在尝试允许value是可选的,如下所示:
struct Dog: Encodable {
// This is the key to the solution: bury the type of value inside a closure
let valueEncoder: (Encoder) throws -> Void
init<T: Encodable>(id: String, value: T?) {
self.valueEncoder = {
var container = $0.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(value, forKey: .value)
}
}
enum CodingKeys: String, CodingKey {
case id, value
}
func encode(to encoder: Encoder) throws {
try valueEncoder(encoder)
}
}
let dogs = [
Dog(id: "123", value: 123),
Dog(id: "456", value: nil),
]
do {
let data = try JSONEncoder().encode(dogs)
print(String(data: data, encoding: .utf8)!)
} catch {
print(error)
}此时,不能再推断T,并引发以下错误:
generic parameter 'T' could not be inferred我正在寻找使用Rob的答案的可能性,如果Optional类型给出了value,则给出以下结果
[{"id":"123","value":123},{"id":"456","value":null}]编辑#1 2020/02/08:解决方案
好的,我是如此专注于给value值nil,以至于我没有意识到nil没有任何类型,从而导致了推理错误。
提供一个可选类型使其工作:
let optString: String? = nil
let dogs = [
Dog(id: "123", value: 123),
Dog(id: "456", value: optString),
]发布于 2020-02-08 07:18:41
以下是另一种可能有帮助的解决方案:
struct Dog<V: Codable>: Codable {
let id: String
let value: V
}https://stackoverflow.com/questions/60096473
复制相似问题