我将Firestore用作数据库,并且想对swift对象(结构类型为enum的var以及相关类型的结构)进行编码。 Firebase具有FieldValue占位符,该占位符允许服务器输入时间戳,而不是在设备上设置时间戳,即FieldValue.serverTimestamp,因此这是我希望与枚举关联值使用的类型。因此,在我要编码的枚举的关联值的类型为FieldValue
以另一种方式返回,数据库已将FieldValue.serverTimestamp设置为时间戳,其类型为Timestamp。因此,要解码的对象必须具有与枚举关联的值作为其他类型-时间戳记?
如何使用Swift 4 Codable协议最好地处理所有这些?香港专业教育学院设法通过创建单独的结构使其工作,枚举可编码和可解码的每种方式,但有很多重复。有什么办法可以让我拥有一个可以满足编译器重新类型但又允许每种方法使用不同类型的结构?
我读过一篇文章,建议我可以使用case FieldValue和case TimeInterval创建一个单独的枚举,并且在主要枚举的associatedValue中成为单个类型,但在我看来,我有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)
}
}
}