我正在尝试扩展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
编码的container.encode(date, forKey: .date)
,则会收到typeMismatch
错误,表明该数据不是Double
。我完全不知道发生了什么,因为encode(to:)
的{{1}}函数显式编码了Double。我尝试通过Swift源代码中的Date
调用来跟踪,并且它似乎没有调用decode
。
所以我想知道,是否有可能改变Date.init(from:Decoder)
类型通过这种扩展解码的方式?我是唯一一个在每个模型中复制自定义Date
解码的选项吗?究竟是什么叫Date
?
答案 0 :(得分:3)
我终于找到了使用以下代码执行此操作的方法:
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条目中编码,所以即使我知道日期条目编码为Double
,它不会让我提取Double
,因为这不是类型标签所说的。我也无法调用decode(Date.self, forKey: key)
的默认实现,因为这是我正在编写的函数,这不是子类,因此我无法调用super
。我尝试了一些聪明的事情,试图从Decoder
中提取具体的KeyedDecodingContainer
,这样我就可以直接调用Date.init(from:Decoder)
,但这不起作用,因为当特定键的上下文丢失时我得到Decoder
了。 (如果您对提取Decoder
s感到好奇,请参阅https://stablekernel.com/understanding-extending-swift-4-codable/。
我知道通过使用Date
周围的包装来进行奇怪的解码,我可以达到我想要的效果,但我不想将.date
附加到我使用日期的所有地方在我的代码库中。然后我意识到,对于这个我坚持使用的默认情况,包装器允许我从SingleValueDecodingContainer
而不是KeyedDecodingContainer
中提取日期,允许我调用默认的{{1}解码代码而不是在调用我的自定义函数的无限循环中结束。
这可能是超级猛烈而且不合适,但它确实有效,并且在我能够将API标准化之前,它将为我节省很多样板。
编辑:我重新安排了一点,以便更好地分工,并使其适用于更多的容器类型
Date