jus*_*ime 2 serialization swift encodable jsonencoder
我正在尝试使用Swift 4的Encodable + JSONEncoder将结构序列化为String.该对象可以保存异类值,如String,Array,Date,Int等.
使用的方法可以正常工作,但Date除外.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))
Run Code Online (Sandbox Code Playgroud)
输出:
"{"key":"bar1","value":"12345"}\n"
"{"key":"bar2","value":12345}\n"
"{"key":"bar3","value":539682026.06086397}\n"
Run Code Online (Sandbox Code Playgroud)
对于bar3对象:我期待类似的东西"{"key":"bar3","value":"Thurs, 3 Jan 1991"}\n",但它以默认的.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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用自定义日期编码策略时,编码器会拦截对Date给定容器中的a 进行编码的调用,然后应用自定义策略.
但是对于你的EncodableValue包装器,你并没有给编码器提供这样做的机会,因为你直接调用底层值的encode(to:)方法.用Date,这将使用其默认表示编码值,其是作为它的timeIntervalSinceReferenceDate.
要解决此问题,您需要在单个值容器中对基础值进行编码,以触发任何自定义编码策略.执行此操作的唯一障碍是协议不符合自身,因此您无法encode(_:)使用Encodable参数调用容器的方法(因为参数采用a <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)
}
}
Run Code Online (Sandbox Code Playgroud)
这利用了这样一个事实,即协议扩展成员有一个隐式<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)
}
}
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,您现在都可以使用此包装器来编码异构编码,同时遵循自定义编码策略:
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"}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1379 次 |
| 最近记录: |