Я использую Firestore в качестве базы данных и хочу кодировать объект swift (структура с типом enum типа со связанным типом).Firebase имеет заполнители FieldValue, которые позволяют серверу вводить временную метку, а не ее установку на устройстве, т.е. FieldValue.serverTimestamp, так что это тип, который я хотел бы иметь для моего значения, связанного с перечислением.Таким образом, в моем объекте для кодирования типом связанного значения перечисления является FieldValue
Возвращаясь другим способом, FieldValue.serverTimestamp был установлен в метку времени базой данных и имеет тип Timestamp.Таким образом, объект для декодирования должен иметь ассоциированное с перечислением значение другого типа - Timestamp?
Как мне лучше всего справиться со всем этим с помощью быстрых 4 кодируемых протоколов ?.Мне удалось заставить его работать, создавая отдельные структуры, перечисляя каждый путь для кодируемого и декодируемого, но есть много повторений.Есть ли способ, которым я могу иметь одну единственную структуру, которая удовлетворяет типам компилятора, но допускает разные типы в каждом случае?
В одной из прочитанных мною статей я могу сделать отдельное перечисление с case FieldValue и case TimeInterval, и оно станет единым типом в ассоциированном значении основного перечисления, но мне кажется, что тогда у меня есть 2 уровня перечисления, которые мне нужнокодировать и декодировать, и я не могу получить работающую часть декодирования?
public struct IDRequest: Codable {
public var id: String?
public var status: [String : IDRequestStatus]}
public enum IDRequestStatus {
case idle
case requested(timestamp: TimestampCase, coord: CLLocationCoordinate2D)
}
// create struct for each enum case with assocvals & make it codable
public struct RequestedKeys: Codable {
var timestamp: TimestampCase
var coord: CLLocationCoordinate2D
}
// extend IDReqStatus & make codable with custom encode & decode
extension IDRequestStatus: Codable {
// define keys to decode for base cases ie idle & requested and keys for the assocvals
private enum CodingKeys: String, CodingKey {
case base // ie idle, requested
case requestedKeys // ie requested assocvals
case payload
}
// create an enum that represents each base case as a simple string
private enum Base: String, Codable {
case idle
case requested
}
public func encode(to encoder: Encoder) throws {
// create the container keyed by coding keys. each case will have a base key and you can alloc specific assocvals to specific extra keys
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .idle:
try container.encode(Base.idle, forKey: .base) // we have a .idle case, encode to use key "idle" (ie Base.idle)
case .requested(let ts, let coord):
try container.encode(Base.requested, forKey: .base) // we have .requested, encode to use key "requested" for the base element of the requested case
try container.encode(RequestedKeys(timestamp: ts, coord: coord), forKey: .requestedKeys) // encode the assocvals using name requestedKeys
}
}
public init(from decoder: Decoder) throws {
// create the container which holds all the elements of your object from the json
let container = try decoder.container(keyedBy: CodingKeys.self)
let base = try container.decode(Base.self, forKey: .base) // decode the base element which tells you what enum case your json data represents
switch base {
case .idle:
self = .idle // if base case was .idle ... create the enum as a .idle (end of story)
case .requested: // if case .requested, decode the data for .requestedKeys to get the data for the assocvals. CANT GET THIS PART WORKING
let requestedParams = try container.decode(RequestedKeys.self, forKey: .requestedKeys)
let ts = try container.decodeIfPresent(TimeInterval.self, forKey: .payload)
self = .requested(timestamp: requestedParams.timestamp, coord: requestedParams.coord) //rehydrate the enum using the data in requestedParams
}
}
}
// test use of TimestampCase
public struct TimestampCaseTest: Codable {
var ts: TimestampCase?
}
// create enum to handle indeterminate types ie diff timestamp type options for codable/decodable
// https://medium.com/makingtuenti/indeterminate-types-with-codable-in-swift-5a1af0aa9f3d
public enum TimestampCase { //dont use TimestampType ... already used by CodableFirebase
case fieldValue(fv: FieldValue)
case timeInterval(ti: TimeInterval)
}
extension TimestampCase: Codable {
private enum CodingKeys: String, CodingKey {
case type
case payload
}
private enum Base: String, Codable {
case fieldValue
case timeInterval
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .fieldValue(let assocVal):
try container.encode(Base.fieldValue, forKey: .type)
try container.encode(assocVal, forKey: .payload)
case .timeInterval(let assocVal):
try container.encode(Base.timeInterval, forKey: .type)
try container.encode(assocVal, forKey: .payload)
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(Base.self, forKey: .type) // extract the type to then switch on
switch type {
case .fieldValue: //fv is not decodable. should never get fv back from firestore
let context = DecodingError.Context(codingPath: [], debugDescription: "FieldValue is not a valid type for decoding")
throw DecodingError.typeMismatch(FieldValue.Type.self, context)
case .timeInterval:
let payload = try container.decode(TimeInterval.self, forKey: .payload)
self = .timeInterval(ti: payload)
}
}
}