如何扩展 Date 和其他内置类型的 Codable 功能?
How to extend Codable functionality of Date and other built in types?
我正在尝试扩展 Date.init(from:Decoder)
的功能以处理从我的服务器传来的不同格式。有时日期将被编码为字符串,有时该字符串嵌套在字典中。根据 Swift 来源,Date
是 decoded/encoded 像:
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(_: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)
编码的 Date
时,我得到一个 typeMismatch
错误,表明数据不是 Double
.我对发生的事情感到非常困惑,因为 Date
的 encode(to:)
函数显式编码了一个 Double。我尝试通过 Swift 源代码中的 decode
调用来追踪我的方式,它似乎从未调用过 Date.init(from:Decoder)
。
所以我想知道,是否可以通过这种扩展来改变 Date
类型的解码方式?我唯一的选择是在每个模型中复制我的自定义 Date
解码吗? 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)
,但这没有用,因为特定键的上下文在我得到了 Decoder
回来。 (如果您对提取 Decoder
感到好奇,请参阅 https://stablekernel.com/understanding-extending-swift-4-codable/)。
我知道我可以通过使用 Date
周围的包装器来进行奇怪的解码来实现我想要的,但我不想将 .date
附加到我需要的所有地方在我的代码库中使用日期。然后我意识到,对于我坚持的这个默认情况,包装器将允许我从 SingleValueDecodingContainer
而不是 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
}
}
我正在尝试扩展 Date.init(from:Decoder)
的功能以处理从我的服务器传来的不同格式。有时日期将被编码为字符串,有时该字符串嵌套在字典中。根据 Swift 来源,Date
是 decoded/encoded 像:
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(_: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)
编码的 Date
时,我得到一个 typeMismatch
错误,表明数据不是 Double
.我对发生的事情感到非常困惑,因为 Date
的 encode(to:)
函数显式编码了一个 Double。我尝试通过 Swift 源代码中的 decode
调用来追踪我的方式,它似乎从未调用过 Date.init(from:Decoder)
。
所以我想知道,是否可以通过这种扩展来改变 Date
类型的解码方式?我唯一的选择是在每个模型中复制我的自定义 Date
解码吗? 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)
,但这没有用,因为特定键的上下文在我得到了 Decoder
回来。 (如果您对提取 Decoder
感到好奇,请参阅 https://stablekernel.com/understanding-extending-swift-4-codable/)。
我知道我可以通过使用 Date
周围的包装器来进行奇怪的解码来实现我想要的,但我不想将 .date
附加到我需要的所有地方在我的代码库中使用日期。然后我意识到,对于我坚持的这个默认情况,包装器将允许我从 SingleValueDecodingContainer
而不是 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
}
}