Это было довольно сложно и весело понять. У нас есть три сложности, которые усложняют это:
- Ключи кодирования переменных
- Ключи кодирования, которые мы также хотим сохранить в качестве значений
- Перечисляемые типы со связанными значениями
Вот мое решение. Это немного долго. Начнем с структуры вашей деятельности:
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
STRUCT. Это потому, что documentId
заполнен ключом вложенного контейнера, который содержит остальные данные. - У нас есть
VariableCodingKeys
, что позволяет нам работать с любым ключом / documentId
.
Наконец, у нас есть ActivityType
enum:
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
}
}
Одна вещь, которая удивила меня, когда я писал это, это то, что не все CodingKeys должны присутствовать для декодер для генерации контейнера с ключами. Я использовал это, чтобы объединить title
и percentage
в одном перечислении. Как и ваше решение, я try
декодирую определенный ключ, проверяю, работает ли он, и продолжаю, если нет.
Я буду первым, кто признает, что это решение , а не короткое. Это работает, хотя, и это круто, как все это работает. Если у вас есть какие-либо вопросы или идеи, чтобы сделать его более кратким, дайте мне знать!