如何解码 Swift 中的结构枚举
How to Decode an Enum of Structs in Swift
很抱歉问了这么长的问题。
我正在使用 Firestore 存储在线数据,当前结构如下;
{
"activities": {
"mG47rRED9Ym4dkXinXrN": {
"createdAt": 1234567890,
"activityType": {
"title": "Some Title"
}
},
"BF3jhINa1qu9kia00BeG": {
"createdAt": 1234567890,
"activityType": {
"percentage": 50,
}
}
}
}
我正在使用 JSON 可解码协议来检索数据。我有一个主要结构;
struct Activity: Decodable {
let documentID: String
let createdAt: Int
let activityType: ActivityType
}
此结构包含强制性数据,例如 createdAt 和 documentID(即 "mG47rRED9Ym4dkXinXrN")。根据嵌套在 "activityType" 中的数据,它应该 return 下面列出的两个结构之一;
struct NewGoal: Decodable {
let title: String
}
struct GoalAchieved: Decodable {
let percentage: Double
}
我正在用可解码枚举来做这个;
enum ActivityType: Decodable {
case newGoal(NewGoal)
case goalAchieved(GoalAchieved)
}
extension ActivityType {
private enum CodingKeys: String, CodingKey {
case activityType
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
if let value = try? values?.decode(GoalAchieved.self, forKey: .activityType) {
self = .goalAchieved(value)
return
}
if let value = try? values?.decode(NewGoal.self, forKey: .activityType) {
self = .newGoal(value)
return
}
throw DecodingError.decoding("Cannot Decode Activity")
}
}
当使用 Activity 结构作为我的数组时,出现解码错误。但是,当使用 ActivityType 作为我的数组时,它可以很好地解码,但不会提供对 documentID 和 createdAt 的访问权限。我无法继承 Activity 结构,因为它是非协议的。请问我该如何构建它?
弄清楚这个问题既棘手又有趣。我们有三个并发症使这变得困难:
- 可变编码键
- 我们也想保留为值的编码键
- 具有关联值的枚举类型
这是我的解决方案。有点长。让我们从您的 activity 结构开始:
struct Activity {
let documentId: String
let createdAt: Int
let activityType: ActivityType
}
很好很简单。现在对于顶级解码容器:
struct Activities: Decodable {
let activities: [Activity]
init(from decoder: Decoder) throws {
var activities: [Activity] = []
let activitiesContainer = try decoder.container(keyedBy: CodingKeys.self)
let container = try activitiesContainer.nestedContainer(keyedBy: VariableCodingKeys.self, forKey: .activities)
for key in container.allKeys {
let activityContainer = try container.nestedContainer(keyedBy: ActivityCodingKeys.self, forKey: key)
let createdAt = try activityContainer.decode(Int.self, forKey: .createdAt)
let activityType = try activityContainer.decode(ActivityType.self, forKey: .activityType)
let activity = Activity(
documentId: key.stringValue,
createdAt: createdAt,
activityType: activityType)
activities.append(activity)
}
self.activities = activities
}
private enum CodingKeys: CodingKey {
case activities
}
private struct VariableCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
return nil
}
}
private enum ActivityCodingKeys: CodingKey {
case createdAt, activityType
}
}
您会注意到几个有趣的点:
ActivityCodingKeys
在 Activity
结构中只有两个字段。这是因为 documentId
填充了嵌套容器的键,其中包含其余数据。
- 我们有
VariableCodingKeys
,这让我们可以使用任何键/documentId
。
最后,我们有 ActivityType
枚举:
enum ActivityType: Decodable {
case newGoal(String), achievedGoal(Double)
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let title = try? container.decode(String.self, forKey: .title) {
self = .newGoal(title)
} else if let percentage = try? container.decode(Double.self, forKey: .percentage) {
self = .achievedGoal(percentage)
} else {
throw DecodingError.keyNotFound(
CodingKeys.title,
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected title or percentage, but found neither."))
}
}
private enum CodingKeys: CodingKey {
case title, percentage
}
}
写这篇文章时让我感到惊讶的一件事是,并非所有 CodingKey 都必须存在,解码器才能生成键控容器。我用它来将 title
和 percentage
组合在一个枚举中。像你的解决方案一样,我 try
解码某个密钥,看看它是否有效,如果无效则继续。
我会第一个承认这个解决方案不短。不过,它确实有效,而且它的工作方式有点酷。如果您有任何使它更简洁的问题或想法,请告诉我!
很抱歉问了这么长的问题。
我正在使用 Firestore 存储在线数据,当前结构如下;
{
"activities": {
"mG47rRED9Ym4dkXinXrN": {
"createdAt": 1234567890,
"activityType": {
"title": "Some Title"
}
},
"BF3jhINa1qu9kia00BeG": {
"createdAt": 1234567890,
"activityType": {
"percentage": 50,
}
}
}
}
我正在使用 JSON 可解码协议来检索数据。我有一个主要结构;
struct Activity: Decodable {
let documentID: String
let createdAt: Int
let activityType: ActivityType
}
此结构包含强制性数据,例如 createdAt 和 documentID(即 "mG47rRED9Ym4dkXinXrN")。根据嵌套在 "activityType" 中的数据,它应该 return 下面列出的两个结构之一;
struct NewGoal: Decodable {
let title: String
}
struct GoalAchieved: Decodable {
let percentage: Double
}
我正在用可解码枚举来做这个;
enum ActivityType: Decodable {
case newGoal(NewGoal)
case goalAchieved(GoalAchieved)
}
extension ActivityType {
private enum CodingKeys: String, CodingKey {
case activityType
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
if let value = try? values?.decode(GoalAchieved.self, forKey: .activityType) {
self = .goalAchieved(value)
return
}
if let value = try? values?.decode(NewGoal.self, forKey: .activityType) {
self = .newGoal(value)
return
}
throw DecodingError.decoding("Cannot Decode Activity")
}
}
当使用 Activity 结构作为我的数组时,出现解码错误。但是,当使用 ActivityType 作为我的数组时,它可以很好地解码,但不会提供对 documentID 和 createdAt 的访问权限。我无法继承 Activity 结构,因为它是非协议的。请问我该如何构建它?
弄清楚这个问题既棘手又有趣。我们有三个并发症使这变得困难:
- 可变编码键
- 我们也想保留为值的编码键
- 具有关联值的枚举类型
这是我的解决方案。有点长。让我们从您的 activity 结构开始:
struct Activity {
let documentId: String
let createdAt: Int
let activityType: ActivityType
}
很好很简单。现在对于顶级解码容器:
struct Activities: Decodable {
let activities: [Activity]
init(from decoder: Decoder) throws {
var activities: [Activity] = []
let activitiesContainer = try decoder.container(keyedBy: CodingKeys.self)
let container = try activitiesContainer.nestedContainer(keyedBy: VariableCodingKeys.self, forKey: .activities)
for key in container.allKeys {
let activityContainer = try container.nestedContainer(keyedBy: ActivityCodingKeys.self, forKey: key)
let createdAt = try activityContainer.decode(Int.self, forKey: .createdAt)
let activityType = try activityContainer.decode(ActivityType.self, forKey: .activityType)
let activity = Activity(
documentId: key.stringValue,
createdAt: createdAt,
activityType: activityType)
activities.append(activity)
}
self.activities = activities
}
private enum CodingKeys: CodingKey {
case activities
}
private struct VariableCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
return nil
}
}
private enum ActivityCodingKeys: CodingKey {
case createdAt, activityType
}
}
您会注意到几个有趣的点:
ActivityCodingKeys
在Activity
结构中只有两个字段。这是因为documentId
填充了嵌套容器的键,其中包含其余数据。- 我们有
VariableCodingKeys
,这让我们可以使用任何键/documentId
。
最后,我们有 ActivityType
枚举:
enum ActivityType: Decodable {
case newGoal(String), achievedGoal(Double)
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let title = try? container.decode(String.self, forKey: .title) {
self = .newGoal(title)
} else if let percentage = try? container.decode(Double.self, forKey: .percentage) {
self = .achievedGoal(percentage)
} else {
throw DecodingError.keyNotFound(
CodingKeys.title,
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected title or percentage, but found neither."))
}
}
private enum CodingKeys: CodingKey {
case title, percentage
}
}
写这篇文章时让我感到惊讶的一件事是,并非所有 CodingKey 都必须存在,解码器才能生成键控容器。我用它来将 title
和 percentage
组合在一个枚举中。像你的解决方案一样,我 try
解码某个密钥,看看它是否有效,如果无效则继续。
我会第一个承认这个解决方案不短。不过,它确实有效,而且它的工作方式有点酷。如果您有任何使它更简洁的问题或想法,请告诉我!