Swift JSONECODER';s日期编码策略不起作用

Swift JSONECODER';s日期编码策略不起作用,swift,serialization,encodable,jsonencoder,Swift,Serialization,Encodable,Jsonencoder,我正在尝试使用Swift 4的Encodable+JSONEncoder将结构序列化为字符串。对象可以保存异类值,如字符串、数组、日期、Int等 所使用的方法效果良好,但日期除外。 JSONECODER的dateEncodingStrategy属性没有任何效果 下面是一个片段,它再现了在操场上的行为: struct EncodableValue:Encodable { var value: Encodable init(_ value: Encodable) {

我正在尝试使用Swift 4的Encodable+JSONEncoder将结构序列化为字符串。对象可以保存异类值,如字符串、数组、日期、Int等

所使用的方法效果良好,但日期除外。 JSONECODER的
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,1991年1月3日”}\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)
        }

    }
}

您可以消除
encodalevalue
包装,改为使用泛型:

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)
结构栏:可编码{
让键:字符串
让值:T?
var-json:String{
let encoder=JSONEncoder()
让dateFormatter=dateFormatter()
dateFormatter.dateFormat=“E,d MMM yyy”
dateFormatter.locale=locale(标识符:“en_US_POSIX”)
编码器.dateEncodingStrategy=.formatted(日期格式化程序)
让data=try!encoder.encode(self)
返回字符串(数据:数据,编码:.utf8)!
}
}
设bar=bar(键:“日期”,值:date())
打印(bar.json)
这将产生:

{"key":"date","value":"Wed, 7 Feb 2018"} {“key”:“date”,“value”:“Wed,2018年2月7日”}
当使用自定义日期编码策略时,编码器将在给定容器中对
日期
进行编码,然后

但是,使用
encodalevelue
包装器,您没有给编码器这样做的机会,因为您直接调用底层值的
encode(to:)
方法。使用
日期
,这是它的

要解决这个问题,您需要在单个值容器中对基础值进行编码,以触发任何自定义编码策略。执行此操作的唯一障碍是这样一个事实,即您不能使用
可编码的
参数调用容器的
编码(:)
方法(因为参数采用

此问题的一个解决方案是定义一个
可编码的
扩展,用于编码到单个值容器中,然后可以在包装器中使用该容器:

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)
  }
}
这利用了这样一个事实,即协议扩展成员有一个隐式的
占位符,其中
p
是被扩展的协议,隐式的
self
参数被键入为这个占位符(长话短说;它允许我们调用
编码(:)
具有可编码的方法(符合类型)

另一种选择是在包装器上有一个通用的初始化器,它通过存储一个执行编码的闭包来进行类型擦除:

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)
  }
}

我已经在Swift 4游乐场检查了你的代码,它给了我关于bar3的结果:{“键”:“bar3”,“值”:“Wed,2018年2月7日”}@ArunKumar-这很奇怪,因为我精确地看到了justintime在使用
EncodeValue
包装器时描述的行为。@ArunKumar你添加了任何额外的代码或更改来纠正这个问题吗?至于我,我也得到了同样的回答。@ShaneD nothing。。。我只是复制了你的代码并粘贴了进去playground@ArunKumar你的XCode版本是什么?我使用的是XCode 9.2,我刚刚运行了XCode 9,它在那里工作。实际上,在更大的画面中,我在另一个结构中使用了Bar作为属性<代码>结构Foo{var bar:[bar]}
,这是我想要序列化的实际结构。如果使用泛型方法,我将无法指定具体类型。最后我会讨论你提出的这个方法的老问题。希望我说的有道理。在这种情况下,假设您不能使用另一个答案中概述的选项2(使用强类型
struct
而不是异构字典或数组集合),那么您可以使用Hamish的方法。我猜这是可行的,因为我们使用泛型来延迟使用运行时提供的值进行编码。因为我尝试了
func encode(to encoder:encoder)抛出{
try container.encode(value)
}
,这导致了错误“无法使用类型为“(Encodable)”的参数列表调用“encode”。干净利落,谢谢@justintime实际上只是想到了一种可能更简洁的方法来使用扩展名:)是的,如果您试图用一个
可编码的
参数调用
编码(u:)
方法,它将不起作用,因为这些方法的形式是
(u值:值)
,而且,
Encodable
目前无法满足这样一个通用占位符的要求,因为。为什么更喜欢扩展方法?@justintime它只是更灵活;对于受约束的泛型初始化器,您需要知道调用站点的具体类型,但是对于使用扩展名的包装器,您可以说
let encodables=[1,“hello”,Date()].map(AnyEncodable.init)
:)
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"}