具有多个键和关联值的可编码枚举
Codable enum with multiple keys and associated values
我看到了关于如何在所有情况下都有关联值时使枚举符合 Codable 的答案,但我不清楚如何混合具有和没有关联值的情况下的枚举:
???如何针对给定案例使用同一密钥的多个变体?
???我如何 encode/decode 个没有关联值的案例?
enum EmployeeClassification : Codable, Equatable {
case aaa
case bbb
case ccc(Int) // (year)
init?(rawValue: String?) {
guard let val = rawValue?.lowercased() else {
return nil
}
switch val {
case "aaa", "a":
self = .aaa
case "bbb":
self = .bbb
case "ccc":
self = .ccc(0)
default: return nil
}
}
// Codable
private enum CodingKeys: String, CodingKey {
case aaa // ??? how can I accept "aaa", "AAA", and "a"?
case bbb
case ccc
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? container.decode(Int.self, forKey: .ccc) {
self = .ccc(value)
return
}
// ???
// How do I decode the cases with no associated value?
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .ccc(let year):
try container.encode(year, forKey: .ccc)
default:
// ???
// How do I encode cases with no associated value?
}
}
}
使用 init 方法的假定原始字符串值作为枚举案例的(字符串)值
enum EmployeeClassification : Codable, Equatable {
case aaa
case bbb
case ccc(Int) // (year)
init?(rawValue: String?) {
guard let val = rawValue?.lowercased() else {
return nil
}
switch val {
case "aaa", "a":
self = .aaa
case "bbb":
self = .bbb
case "ccc":
self = .ccc(0)
default: return nil
}
}
// Codable
private enum CodingKeys: String, CodingKey { case aaa, bbb, ccc }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? container.decode(Int.self, forKey: .ccc) {
self = .ccc(value)
} else if let aaaValue = try? container.decode(String.self, forKey: .aaa), ["aaa", "AAA", "a"].contains(aaaValue) {
self = .aaa
} else if let bbbValue = try? container.decode(String.self, forKey: .bbb), bbbValue == "bbb" {
self = .bbb
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Data doesn't match"))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .aaa: try container.encode("aaa", forKey: .aaa)
case .bbb: try container.encode("bbb", forKey: .bbb)
case .ccc(let year): try container.encode(year, forKey: .ccc)
}
}
}
解码错误非常普遍。您可以为每个 CodingKey
抛出更具体的错误
感谢@vadian 的精彩回答
如何为具有关联值和(或)空情况的任何枚举实现自定义 Decodable / Encodable 方法的另一种方法 – 使用从根 container
调用的 nestedContainer
方法的方法。
这种方式在 Swift Evolution proposal for Swift 5.5 中描述了支持自动合成 Codable
符合具有关联值的枚举。
我从提案中得到的所有细节和下一个实现你也可以看看:https://github.com/apple/swift-evolution/blob/main/proposals/0295-codable-synthesis-for-enums-with-associated-values.md
我还扩展了提案中的示例以准确涵盖作者的问题。
enum Command: Codable {
case load(String)
case store(key: String, Int)
case eraseAll
}
Encodable
的 encode(to:)
实现如下所示:
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .load(key):
var nestedContainer = container.nestedUnkeyedContainer(forKey: .load)
try nestedContainer.encode(key)
case let .store(key, value):
var nestedContainer = container.nestedContainer(keyedBy: StoreCodingKeys.self, forKey: .store)
try nestedContainer.encode(key, forKey: .key)
try nestedContainer.encode(value, forKey: .value)
case .eraseAll:
var nestedContainer = container.nestedUnkeyedContainer(forKey: .eraseAll)
try nestedContainer.encodeNil()
}
}
请注意一些修改:(1)对于 load
和 eraseAll
的情况,我使用 nestedUnkeyedContainer
而不是提案中建议的 eraseAll
是新的情况我也添加了没有关联的值。
和 Decodable
的 init(from:)
实现如下所示:
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.allKeys.count != 1 {
let context = DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Invalid number of keys found, expected one.")
throw DecodingError.typeMismatch(Command.self, context)
}
switch container.allKeys.first.unsafelyUnwrapped {
case .load:
let nestedContainer = try container.nestedUnkeyedContainer(forKey: .load)
self = .load(try nestedContainer.decode(String.self))
case .store:
let nestedContainer = try container.nestedContainer(keyedBy: StoreCodingKeys.self, forKey: .store)
self = .store(
key: try nestedContainer.decode(String.self, forKey: .key),
value: try nestedContainer.decode(Int.self, forKey: .value))
case .eraseAll:
_ = try container.nestedUnkeyedContainer(forKey: .eraseAll)
self = .eraseAll
}
}
从 Swift 5.5 开始,具有关联值的枚举获得了自动符合 Codable
的能力。有关实施细节的更多详细信息,请参阅 this swift-进化提案。
所以,这对你的枚举来说已经足够了:
enum EmployeeClassification : Codable, Equatable {
case aaa
case bbb
case ccc(Int) // (year)
不再CodingKeys
,不再init(from:)
,或encode(to:)
我看到了关于如何在所有情况下都有关联值时使枚举符合 Codable 的答案,但我不清楚如何混合具有和没有关联值的情况下的枚举:
???如何针对给定案例使用同一密钥的多个变体?
???我如何 encode/decode 个没有关联值的案例?
enum EmployeeClassification : Codable, Equatable {
case aaa
case bbb
case ccc(Int) // (year)
init?(rawValue: String?) {
guard let val = rawValue?.lowercased() else {
return nil
}
switch val {
case "aaa", "a":
self = .aaa
case "bbb":
self = .bbb
case "ccc":
self = .ccc(0)
default: return nil
}
}
// Codable
private enum CodingKeys: String, CodingKey {
case aaa // ??? how can I accept "aaa", "AAA", and "a"?
case bbb
case ccc
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? container.decode(Int.self, forKey: .ccc) {
self = .ccc(value)
return
}
// ???
// How do I decode the cases with no associated value?
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .ccc(let year):
try container.encode(year, forKey: .ccc)
default:
// ???
// How do I encode cases with no associated value?
}
}
}
使用 init 方法的假定原始字符串值作为枚举案例的(字符串)值
enum EmployeeClassification : Codable, Equatable {
case aaa
case bbb
case ccc(Int) // (year)
init?(rawValue: String?) {
guard let val = rawValue?.lowercased() else {
return nil
}
switch val {
case "aaa", "a":
self = .aaa
case "bbb":
self = .bbb
case "ccc":
self = .ccc(0)
default: return nil
}
}
// Codable
private enum CodingKeys: String, CodingKey { case aaa, bbb, ccc }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? container.decode(Int.self, forKey: .ccc) {
self = .ccc(value)
} else if let aaaValue = try? container.decode(String.self, forKey: .aaa), ["aaa", "AAA", "a"].contains(aaaValue) {
self = .aaa
} else if let bbbValue = try? container.decode(String.self, forKey: .bbb), bbbValue == "bbb" {
self = .bbb
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Data doesn't match"))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .aaa: try container.encode("aaa", forKey: .aaa)
case .bbb: try container.encode("bbb", forKey: .bbb)
case .ccc(let year): try container.encode(year, forKey: .ccc)
}
}
}
解码错误非常普遍。您可以为每个 CodingKey
抛出更具体的错误感谢@vadian 的精彩回答
如何为具有关联值和(或)空情况的任何枚举实现自定义 Decodable / Encodable 方法的另一种方法 – 使用从根 container
调用的 nestedContainer
方法的方法。
这种方式在 Swift Evolution proposal for Swift 5.5 中描述了支持自动合成 Codable
符合具有关联值的枚举。
我从提案中得到的所有细节和下一个实现你也可以看看:https://github.com/apple/swift-evolution/blob/main/proposals/0295-codable-synthesis-for-enums-with-associated-values.md
我还扩展了提案中的示例以准确涵盖作者的问题。
enum Command: Codable {
case load(String)
case store(key: String, Int)
case eraseAll
}
Encodable
的 encode(to:)
实现如下所示:
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .load(key):
var nestedContainer = container.nestedUnkeyedContainer(forKey: .load)
try nestedContainer.encode(key)
case let .store(key, value):
var nestedContainer = container.nestedContainer(keyedBy: StoreCodingKeys.self, forKey: .store)
try nestedContainer.encode(key, forKey: .key)
try nestedContainer.encode(value, forKey: .value)
case .eraseAll:
var nestedContainer = container.nestedUnkeyedContainer(forKey: .eraseAll)
try nestedContainer.encodeNil()
}
}
请注意一些修改:(1)对于 load
和 eraseAll
的情况,我使用 nestedUnkeyedContainer
而不是提案中建议的 eraseAll
是新的情况我也添加了没有关联的值。
和 Decodable
的 init(from:)
实现如下所示:
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.allKeys.count != 1 {
let context = DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Invalid number of keys found, expected one.")
throw DecodingError.typeMismatch(Command.self, context)
}
switch container.allKeys.first.unsafelyUnwrapped {
case .load:
let nestedContainer = try container.nestedUnkeyedContainer(forKey: .load)
self = .load(try nestedContainer.decode(String.self))
case .store:
let nestedContainer = try container.nestedContainer(keyedBy: StoreCodingKeys.self, forKey: .store)
self = .store(
key: try nestedContainer.decode(String.self, forKey: .key),
value: try nestedContainer.decode(Int.self, forKey: .value))
case .eraseAll:
_ = try container.nestedUnkeyedContainer(forKey: .eraseAll)
self = .eraseAll
}
}
从 Swift 5.5 开始,具有关联值的枚举获得了自动符合 Codable
的能力。有关实施细节的更多详细信息,请参阅 this swift-进化提案。
所以,这对你的枚举来说已经足够了:
enum EmployeeClassification : Codable, Equatable {
case aaa
case bbb
case ccc(Int) // (year)
不再CodingKeys
,不再init(from:)
,或encode(to:)