我正在尝试使用swift 4的可编码+jsonEncoder将结构序列化为字符串。对象可以保存不同的值,如字符串、数组、日期、int等。
除日期外,使用的方法工作正常。
jsonEncoder的dateEncodingStrategy
属性没有任何效果。
以下是一个重现操场行为的片段:
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策略格式返回日期。##编辑1##
所以我在Xcode9中运行了相同的代码,它给出了预期的输出,也就是正确地将日期格式化为字符串。
我想9.2有一个小的升级到Swift4,这打破了这个功能。不知道下一步该怎么做。
##编辑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)
}
}
}
最佳答案
当使用自定义日期编码策略时,编码器intercepts calls将给定容器中的Date
编码,然后applies the custom strategy。
但是,使用您的EncodableValue
包装器,您不会给编码器这样做的机会,因为您直接调用底层值的encode(to:)
方法。对于Date
,这个will encode the value using its default representation,就是它的timeIntervalSinceReferenceDate
。
要解决此问题,需要在单个值容器中对基础值进行编码,以触发任何自定义编码策略。这样做的唯一障碍是protocols don't conform to themselves这一事实,因此您不能使用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"}
关于swift - JSONEncoder dateEncodingStrategy无法正常工作,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48658574/