当使用自定义日期编码策略时,编码器拦截呼叫编码一个Date
在给定的容器中,然后应用自定义策略.
然而与你的EncodableValue
包装器,您没有给编码器执行此操作的机会,因为您直接调用底层值encode(to:)
方法。和Date
, this 将使用其默认表示形式对值进行编码,正如它的timeIntervalSinceReferenceDate.
要解决此问题,您需要在单个值容器中对基础值进行编码以触发任何自定义编码策略。这样做的唯一障碍是协议本身不符合,所以你不能调用容器的encode(_:)
方法与Encodable
参数(因为参数采用<Value : Encodable>
).
解决这个问题的一个方法是定义一个Encodable
用于编码到单值容器中的扩展,然后您可以在包装器中使用它:
extension Encodable {
fileprivate func encode(to container: inout SingleValueEncodingContainer) throws {
try container.encode(self)
}
}
struct AnyEncodable : Encodable {
var value: Encodable
init(_ value: Encodable) {
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try value.encode(to: &container)
}
}
这利用了协议扩展成员具有隐式的事实<Self : P>
占位符在哪里P
是正在扩展的协议,并且隐式self
参数被输入为这个占位符(长话短说;它允许我们调用encode(_:)
方法与Encodable
符合类型)。
另一种选择是在包装器上有一个通用初始化器,通过存储执行编码的闭包来擦除类型:
struct AnyEncodable : Encodable {
private let _encodeTo: (Encoder) throws -> Void
init<Value : Encodable>(_ value: Value) {
self._encodeTo = { encoder in
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
func encode(to encoder: Encoder) throws {
try _encodeTo(encoder)
}
}
在这两种情况下,您现在可以使用此包装器对异构可编码进行编码,同时遵守自定义编码策略:
import Foundation
struct Bar : Encodable, CustomStringConvertible {
let key: String
let value: AnyEncodable
var description: String {
let encoder = JSONEncoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "E, d MMM yyyy"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
encoder.dateEncodingStrategy = .formatted(dateFormatter)
guard let jsonData = try? encoder.encode(self) else {
return "Bar(key: \(key as Any), value: \(value as Any))"
}
return String(decoding: jsonData, as: UTF8.self)
}
}
print(Bar(key: "bar1", value: AnyEncodable("12345")))
// {"key":"bar1","value":"12345"}
print(Bar(key: "bar2", value: AnyEncodable(12345)))
// {"key":"bar2","value":12345}
print(Bar(key: "bar3", value: AnyEncodable(Date())))
// {"key":"bar3","value":"Wed, 7 Feb 2018"}