很长的问题的道歉。
我正在使用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”中的数据,它应该返回下面列出的两个结构之一;
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结构用作数组时,我收到DecodingError。但是,当使用ActivityType作为我的数组时,它会很好地解码,但不会提供对documentID&createdAt的访问。我不能继承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
解码某个密钥,看看它是否有效,否则继续进行。
我将是第一个承认此解决方案简短的[[not
。不过,它确实可以工作,而且一切运作起来都很酷。如果您有任何疑问或想法要使其更简洁,请告诉我!