Swift 4: кодируемыйКодирование и декодирование с другим типом объекта.Firestore - PullRequest
0 голосов
/ 07 декабря 2018

Я использую 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)
        }
    }
}
...