Swift 如何扩展日期和其他内置类型的可编码功能?
我正在尝试扩展Swift 如何扩展日期和其他内置类型的可编码功能?,swift,extension-methods,decodable,Swift,Extension Methods,Decodable,我正在尝试扩展Date.init(from:Decoder)的功能,以处理从服务器传递的不同格式。有时日期将被编码为字符串,有时该字符串嵌套在字典中。根据Swift来源,日期被解码/编码如下: extension Date : Codable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let timesta
Date.init(from:Decoder)
的功能,以处理从服务器传递的不同格式。有时日期将被编码为字符串,有时该字符串嵌套在字典中。根据Swift来源,日期
被解码/编码如下:
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
以更改decode(u:forKey)
中的Date
解码,如下所示:
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)
}
}
}
但是,当调用它来解码我通过调用container.encode(Date,forKey:.Date)编码的日期时,我会得到一个类型不匹配
错误,即数据不是双精度
。我完全搞不懂发生了什么,因为日期的编码(to:)
函数显式地对双精度编码。我试图通过Swift源代码中的decode
调用追踪我的路径,但它似乎从未调用Date.init(from:Decoder)
所以我想知道,是否有可能通过这种扩展来改变日期
类型的解码方式?我是否可以在每个型号中复制自定义的日期
解码?究竟是什么调用了init(from:Decoder)
?我终于想出了一种方法,用下面的代码来实现这一点:
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
。我尝试了一些聪明的方法,试图从KeyedDecodingContainer
中提取具体的Decoder
,这样我就可以直接调用Date.init(from:Decoder)
,但这不起作用,因为当我拿回解码器时,特定密钥的上下文丢失了。(查看您是否对提取解码器
s感兴趣)
我知道我可以通过使用Date
的包装器来完成奇怪的解码,但我不想在代码库中使用日期的所有地方都附加.Date
。然后我意识到,对于我一直坚持的这个默认情况,包装器将允许我从SingleValueDecodingContainer
而不是从KeyedDecodingContainer
提取日期,从而允许我调用默认的日期
解码代码,而不会在调用自定义函数的无限循环中结束
这可能是超级jank和不合适的,但它可以工作,并将为我节省大量的样板文件,直到我可以使我的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
}
}
我终于找到了一种使用以下代码实现这一点的方法:
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
。我尝试了一些聪明的方法,试图从KeyedDecodingContainer
中提取具体的Decoder
,这样我就可以直接调用Date.init(from:Decoder)
,但这不起作用,因为当我拿回解码器时,特定密钥的上下文丢失了。(查看您是否对提取解码器
s感兴趣)
我知道我可以通过使用Date
的包装器来完成奇怪的解码,但我不想在代码库中使用日期的所有地方都附加.Date
。然后我意识到,对于我一直坚持的这个默认情况,包装器将允许我从SingleValueDecodingContainer
而不是从KeyedDecodingContainer
提取日期,从而允许我调用默认的日期
解码代码,而不会在调用自定义函数的无限循环中结束
这可能是超级jank和不合适的,但它可以工作,并将为我节省大量的样板文件,直到我可以使我的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
}
}
您不能通过在扩展中提供自己的版本来覆盖其他人的方法。如果您使用的是JSONDecoder
,有什么原因不能使用它吗?如果没有,或者您正试图为所有编码器和解码器解决此问题-写入解决方案是编写一个适配器类型,它包装日期,并提供自己的init(from:)
,如上所述。然后,在编码/解码之前,将日期
包装在适配器类型中。我不能使用日期解码策略
,因为我需要一个可以与所有解码器一起工作的解决方案,并且可以处理从我使用的API传递的嵌套日期对象。我想你是对的,我只需要创建一个Date
的子类,而不是试图添加到默认的Date
行为中,我越深入地研究它,就越觉得我的方法不正确。因为Date
是一个struct
而不是一个类,所以你不能对它进行子类化-