1

我正在尝试扩展功能Date.init(from:Decoder)以处理从我的服务器传递的不同格式。有时日期将被编码为字符串,有时该字符串嵌套在字典中。根据 Swift 来源,Date解码/编码如下:

extension Date : Codable {
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let timestamp = try container.decode(Double.self)
        self.init(timeIntervalSinceReferenceDate: timestamp)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(self.timeIntervalSinceReferenceDate)
    }
}

因此,我尝试按如下方式扩展该功能:

public extension Date {

    private enum CodingKeys: String, CodingKey {
        case datetime
    }

    public init(from decoder: Decoder) throws {
        let dateString: String
        if let container = try? decoder.container(keyedBy: CodingKeys.self) {
            dateString = try container.decode(String.self, forKey: .datetime)
        } else if let string = try? decoder.singleValueContainer().decode(String.self) {
            dateString = string
        } else {
            let timestamp = try decoder.singleValueContainer().decode(Double.self)
            self.init(timeIntervalSinceReferenceDate: timestamp)
            return
        }
        if let date = Utils.date(from: dateString) {
            self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
        } else if let date = Utils.date(from: dateString, with: "yyyy-MM-dd") {
            self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
        } else {
            let context = DecodingError.Context(codingPath: [], debugDescription: "Date format was unparseable.")
            throw DecodingError.dataCorrupted(context)
        }
    }

}

然而,这个函数永远不会被调用。然后我尝试扩展KeyedDecodingContainer以更改Date解码decode(_:forKey)如下:

extension KeyedDecodingContainer {

    private enum TimeCodingKeys: String, CodingKey {
        case datetime
    }

    func decode(_ type: Date.Type, forKey key: K) throws -> Date {
        let dateString: String
        if let timeContainer = try? self.nestedContainer(keyedBy: TimeCodingKeys.self, forKey: key) {
            dateString = try timeContainer.decode(String.self, forKey: .datetime)
        } else if let string = try? self.decode(String.self, forKey: key) {
            dateString = string
        } else {
            let value = try self.decode(Double.self, forKey: key)
            return Date(timeIntervalSinceReferenceDate: value)
        }
        if let date = Utils.date(from: dateString) {
            return date
        } else if let date = Utils.date(from: dateString, with: Globals.standardDateFormat) {
            return date
        } else {
            let context = DecodingError.Context(codingPath: [], debugDescription: "Date format was not parseable.")
            throw DecodingError.dataCorrupted(context)
        }
    }

}

但是,当调用它来解码Date我通过调用编码的a 时container.encode(date, forKey: .date),我收到一个typeMismatch错误,即数据不是 a Double。我完全不知道发生了什么,因为encode(to:)函数Date显式地编码了一个 Double。我尝试通过 Swift 源代码中的调用来追踪我的方式decode,但它似乎从来没有调用Date.init(from:Decoder).

所以我想知道,是否可以Date通过这种扩展来改变类型的解码方式?我唯一的选择是Date在每个模型中复制我的自定义解码吗?到底叫init(from:Decoder)什么?

4

1 回答 1

4

我终于想出了一个方法来用下面的代码做到这一点:

fileprivate struct DateWrapper: Decodable {
    var date: Date

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        date = try container.decode(Date.self)
    }
}

extension KeyedDecodingContainer {

    private enum TimeCodingKeys: String, CodingKey {
        case datetime
    }

    func decode(_ type: Date.Type, forKey key: K) throws -> Date {
        let dateString: String
        if let timeContainer = try? self.nestedContainer(keyedBy: TimeCodingKeys.self, forKey: key) {
            dateString = try timeContainer.decode(String.self, forKey: .datetime)
        } else if let string = try? self.decode(String.self, forKey: key) {
            dateString = string
        } else {
            return try self.decode(DateWrapper.self, forKey: key).date
        }
        if let date = Utils.date(from: dateString) {
            return date
        } else if let date = Utils.date(from: dateString, with: "yyyy-MM-dd") {
            return date
        } else {
            let context = DecodingError.Context(codingPath: [], debugDescription: "Date format was not parseable.")
            throw DecodingError.dataCorrupted(context)
        }
    }

}

尝试重新创建代码的问题Date.init(from:Decoder)是类型信息也编码在 plist 条目中,所以即使我知道日期的条目被编码为 a Double,它也不会让我提取 aDouble因为那不是类型标签说。我也无法调用的默认实现,decode(Date.self, forKey: key)因为那是我正在编写的函数,而且这不是子类,所以我不能调用super. 我尝试了一些巧妙的方法,试图从中提取具体内容DecoderKeyedDecodingContainer以便我可以Date.init(from:Decoder)直接调用,但这不起作用,因为当我Decoder返回时,特定键的上下文丢失了。(如果您对提取s感到好奇,请参阅https://stablekernel.com/understanding-extending-swift-4-codable/ )。Decoder

我知道我可以通过使用包装器进行奇怪的解码来实现我想要的Date,但我不想.date在我的代码库中使用日期的所有地方都附加。然后我意识到,对于我坚持的这种默认情况,包装器将允许我从 aSingleValueDecodingContainer而不是从 a中提取日期KeyedDecodingContainer,从而允许我调用默认Date解码代码,而不会在调用我的自定义函数的无限循环中结束。

这可能是超级垃圾和不合适的,但它有效,并且会为我节省很多样板,直到我可以让我的 API 标准化。

编辑:我重新安排了这一点,以便更好地划分职责,并使其适用于更多容器类型

fileprivate struct DateWrapper: Decodable {

    var date: Date

    private enum TimeCodingKeys: String, CodingKey {
        case datetime
    }

    init(from decoder: Decoder) throws {
        let dateString: String
        if let timeContainer = try? decoder.container(keyedBy: TimeCodingKeys.self) {
            dateString = try timeContainer.decode(String.self, forKey: .datetime)
        } else {
            let container = try decoder.singleValueContainer()
            if let string = try? container.decode(String.self) {
                dateString = string
            } else {
                date = try container.decode(Date.self)
                return
            }
        }
        if let date = Utils.date(from: dateString) {
            self.date = date
        } else if let date = Utils.date(from: dateString, with: "yyyy-MM-dd") {
            self.date = date
        } else {
            let context = DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Date format was not parseable.")
            throw DecodingError.dataCorrupted(context)
        }
    }
}

extension KeyedDecodingContainer {

    func decode(_ type: Date.Type, forKey key: K) throws -> Date {
        return try self.decode(DateWrapper.self, forKey: key).date
    }

    func decode(_ type: [Date].Type, forKey key: K) throws -> [Date] {
        var container = try nestedUnkeyedContainer(forKey: key)
        var dates: [Date] = []
        while !container.isAtEnd {
            dates.append(try container.decode(Date.self))
        }
        return dates
    }

}

extension UnkeyedDecodingContainer {

    mutating func decode(_ type: Date.Type) throws -> Date {
        return try self.decode(DateWrapper.self).date
    }

}
于 2018-02-16T23:40:34.927 回答