JSONEncoder 的 dateEncodingStrategy 不工作
JSONEncoder's dateEncodingStrategy not working
我正在尝试使用 Swift 4 的 Encodable+JSONEncoder 将结构序列化为字符串。该对象可以保存异构值,如字符串、数组、日期、整数等。
使用的方法工作正常,但日期除外。
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"
的结果,但它 returns 默认 .deferToDate 策略格式中的日期。
##编辑 1##
所以我 运行 在 XCode 9 中使用了相同的代码,它给出了预期的输出,即将日期正确格式化为字符串。
我认为 9.2 对 Swift 4 进行了小幅升级,这破坏了此功能。不确定下一步该做什么。
##编辑 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)
}
}
}
您可以去掉 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":"Wed, 7 Feb 2018"}
使用自定义日期编码策略时,编码器 intercepts calls to encode a Date
in a given container and then applies the custom strategy。
但是,对于 EncodableValue
包装器,您没有给编码器执行此操作的机会,因为您是直接调用基础值的 encode(to:)
方法。有了 Date
,这个 will encode the value using its default representation, which is as its 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"}
我正在尝试使用 Swift 4 的 Encodable+JSONEncoder 将结构序列化为字符串。该对象可以保存异构值,如字符串、数组、日期、整数等。
使用的方法工作正常,但日期除外。
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"
的结果,但它 returns 默认 .deferToDate 策略格式中的日期。
##编辑 1##
所以我 运行 在 XCode 9 中使用了相同的代码,它给出了预期的输出,即将日期正确格式化为字符串。 我认为 9.2 对 Swift 4 进行了小幅升级,这破坏了此功能。不确定下一步该做什么。
##编辑 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)
}
}
}
您可以去掉 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":"Wed, 7 Feb 2018"}
使用自定义日期编码策略时,编码器 intercepts calls to encode a Date
in a given container and then applies the custom strategy。
但是,对于 EncodableValue
包装器,您没有给编码器执行此操作的机会,因为您是直接调用基础值的 encode(to:)
方法。有了 Date
,这个 will encode the value using its default representation, which is as its 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"}