我正在尝试使用 Swift 4 的 Encodable+JSONEncoder 将结构序列化为字符串。该对象可以保存异构值,如字符串、数组、日期、Int 等。
使用的方法工作正常,但日期除外。 JSONEncoder 的dateEncodingStrategy
属性没有任何效果。
下面是一个重现 Playground 中行为的片段:
struct EncodableValue:Encodable {
var value: Encodable
init(_ value: Encodable) {
self.value = value
}
func encode(to encoder: Encoder) throws {
try value.encode(to: encoder)
}
}
struct Bar: Encodable, CustomStringConvertible {
let key: String?
let value: EncodableValue?
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)
let jsonData = try? encoder.encode(self)
return String(data: jsonData!, encoding: .utf8)!
}
}
let bar1 = Bar(key: "bar1", value: EncodableValue("12345"))
let bar2 = Bar(key: "bar2", value: EncodableValue(12345))
let bar3 = Bar(key: "bar3", value: EncodableValue(Date()))
print(String(describing: bar1))
print(String(describing: bar2))
print(String(describing: bar3))
输出:
"{"key":"bar1","value":"12345"}n"
"{"key":"bar2","value":12345}n"
"{"key":"bar3","value":539682026.06086397}n"
对于 bar3 对象:我期待类似"{"key":"bar3","value":"Thurs, 3 Jan 1991"}n"
的东西,但它以默认的 .deferToDate 策略格式返回日期。
##EDIT 1##
所以我在 XCode 9 中运行了相同的代码,它给出了预期的输出,即正确将日期格式化为字符串。 我认为 9.2 对 Swift 4 进行了小幅升级,这破坏了此功能。不知道下一步该怎么做。
##EDIT 2##
作为临时补救措施,我在更改为使用闭包的 @Hamish 方法之前使用了以下代码片段。
struct EncodableValue:Encodable {
var value: Encodable
init(_ value: Encodable) {
self.value = value
}
func encode(to encoder: Encoder) throws {
if let date = value as? Date {
var container = encoder.singleValueContainer()
try container.encode(date)
}
else {
try value.encode(to: encoder)
}
}
}
使用自定义日期编码策略时,编码器会截获对给定容器中的Date
进行编码的调用,然后应用自定义策略。
但是,对于EncodableValue
包装器,您没有给编码器执行此操作的机会,因为您直接调用基础值的encode(to:)
方法。对于Date
,这将使用其默认表示形式对值进行编码,该表示形式作为其timeIntervalSinceReferenceDate
。
若要解决此问题,需要在单个值容器中对基础值进行编码,以触发任何自定义编码策略。这样做的唯一障碍是协议不符合自身,因此您不能使用Encodable
参数调用容器的encode(_:)
方法(因为参数采用<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
参数键入为此占位符(长话短说;它允许我们使用符合Encodable
的类型调用encode(_:)
方法)。
另一种选择是在包装器上有一个通用初始化器,该初始化器通过存储执行编码的闭包来擦除:
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"}
您可以消除EncodableValue
包装器,改用泛型:
struct Bar<T: Encodable>: Encodable {
let key: String
let value: T?
var json: String {
let encoder = JSONEncoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "E, d MMM yyyy"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
encoder.dateEncodingStrategy = .formatted(dateFormatter)
let data = try! encoder.encode(self)
return String(data: data, encoding: .utf8)!
}
}
let bar = Bar(key: "date", value: Date())
print(bar.json)
这会产生:
{"key":"date","value":"周三, 7 Feb 2018"}