[英]Using CodingKeys with custom protocol
我有以下 Codable 协议,其中包含一个我想从可编码协议中排除的变量。
问题是我不能在我自己的协议中使用为此创建的 CodingKeys 枚举: Type 'CodingKeys' cannot be nested in protocol 'Animal'
。
protocol Animal: Codable {
var name: String { get set }
var color: String { get }
var selfiePicture: Selfie { get }
// Not possible
enum CodingKeys: String, CodingKey {
case name
case color
}
}
我该如何解决这个问题?
编辑更多代码和更具体的例子
Animal
被多个结构使用(不能是类):
struct Frog: Animal {
var name: String
var color: String
// extra variables on top of Animal's ones
var isPoisonous: Bool
var selfiePicture = [...]
}
它还用作另一个可顶部编码的 object 上的变量数组:
final class Farm: Codable {
var address: String
// more variables
var animals: [Animal]
enum CodingKeys: String, CodingKey {
case address
case animals
}
convenience init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
address = try values.decode(String.self, forKey: .address)
animals = try values.decode([Animal].self, forKey: .animals) // ERROR --> Protocol 'Animal' as a type cannot conform to 'Decodable'
}
}
解决此问题的一种方法是使用组合,将公共属性移动到新类型并在协议中使用该类型。
因此,让我们为公共属性创建一个类型,并让该类型保存CodingKey
枚举
struct AnimalCommon: Codable {
var name: String
var color: String
var selfiePicture: Selfie = Selfie()
enum CodingKeys: String, CodingKey {
case name
case color
}
}
协议变为
protocol Animal: Codable {
var common: AnimalCommon { get set }
}
之后,实现实际的 Animal 类型将非常容易,例如
struct Frog: Animal {
var common: AnimalCommon
var isPoisonous: Bool
}
let frog = Frog(common: AnimalCommon(name: "kermit", color: "green"), isPoisonous: false)
do {
let data = try JSONEncoder().encode(frog)
if let string = String(data: data, encoding: .utf8) { print(frog) }
} catch {
print(error)
}
您还可以使用计算属性向协议添加扩展,以便您可以直接访问属性,即frog.name = "Kermit"
extension Animal {
var name: String {
get {
common.name
}
set {
common.name = newValue
}
}
var color: String {
common.color
}
}
遵循协议类型不能符合协议,因为只有具体类型才能符合协议,我不得不放弃protocol
并在内部使用struct
+ enum
。
尽管@JoakimDanielson 的回答很有希望,但它并不能解决我在尝试从我的Farm
class 解码Animal
数组时遇到的错误: Protocol 'Animal' as a type cannot conform to 'Decodable'
。
这是我的 model 最后的样子:
struct Animal: Codable {
enum Species: Codable {
case frog(FrogSpecificities)
case ...
var selfiePicture: Selfie {
switch self {
case .frog(let frogSpecificities):
return frogSpecificities.selfie
case ...
...
}
}
enum CodingKeys: String, CodingKey {
case FrogSpecificities
case ...
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let frogSpecificities = try? values.decode(FrogSpecificities.self, forKey: .frogSpecificities) {
self = .frog(frogSpecificities)
} else if ...
...
} else {
// throw an error here if no case was decodable
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .frog(let frogSpecificities):
try container.encode(frogSpecificities, forKey: .frogSpecificities)
case ...:
...
}
}
}
var name: String
let color: String
var species: Species
enum CodingKeys: String, CodingKey {
case name
case color
case specificities
}
}
struct FrogSpecificities: Codable {
let isPoisonous: Bool
let selfie = Selfie()
enum CodingKeys: String, CodingKey {
case isPoisonous
}
}
final class Farm: Codable {
var address: String
var animals: [Animal]
enum CodingKeys: String, CodingKey {
case address
case animals
}
convenience init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
address = try values.decode(String.self, forKey: .address)
animals = try values.decode([Animal].self, forKey: .animals) // Works fine
}
}
我的Farm
object 现在可以包含一个Animal
arrays,每个动物都有特定的可编码struct
。 它还可以包含不可编码的变量。 我可以像这样访问我的动物的每个特性:
if let firstAnimel = MyFarm.animals.firstObject,
case .frog(let frogSpecificities) = firstAnimal.species {
print(frogSpecificities.isPoisonous)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.