JSONEncoder的dateEncodingStrategy无法正常工作 [英] JSONEncoder's dateEncodingStrategy not working

查看:111
本文介绍了JSONEncoder的dateEncodingStrategy无法正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用Swift 4的Encodable + JSONEncoder将结构序列化为String.该对象可以包含异类值,例如String,Array,Date,Int等.

I am trying to serialize a struct to a String using Swift 4's Encodable+JSONEncoder. The object can hold heterogenous values like String, Array, Date, Int etc.

除了Date以外,所使用的方法都可以正常工作. JSONEncoder的dateEncodingStrategy属性没有任何作用.

The used approach works fine with the exception of Date. JSONEncoder's dateEncodingStrategy property is not having any effect.

这是一个片段,可再现Playground中的行为:

Here is a snippet which reproduces the behaviour in 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策略格式返回日期.

For bar3 object: I'm expecting something like "{"key":"bar3","value":"Thurs, 3 Jan 1991"}\n", but it returns the date in the default .deferToDate strategy format.

## EDIT 1 ##

所以我在XCode 9中运行了相同的代码,它提供了预期的输出,即正确地将日期格式化为字符串. 我认为9.2对Swift 4进行了次要升级,从而破坏了此功能.不知道下一步该怎么做.

So I ran the same code in XCode 9 and it gives the expected output, i.e. correctly formats the date to string. I'm thinking 9.2 has a minor upgrade to Swift 4 which is breaking this feature. Not sure what to do next.

## EDIT 2 ##

作为临时补救措施,在使用闭包更改为@Hamish的方法之前,我使用了以下代码段.

As a temp remedy I'd used the following snippet before changing to @Hamish's approach using a closure.

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

    }
}

推荐答案

使用自定义日期编码策略时,编码器

When using a custom date encoding strategy, the encoder intercepts calls to encode a Date in a given container and then applies the custom strategy.

但是,对于您的EncodableValue包装器,您没有给编码器执行此操作的机会,因为您直接调用基础值的encode(to:)方法.使用Date,此会进行编码值使用其默认表示形式,即其 timeIntervalSinceReferenceDate .

However with your EncodableValue wrapper, you're not giving the encoder the chance to do this because you're calling directly into the underlying value's encode(to:) method. With Date, this will encode the value using its default representation, which is as its timeIntervalSinceReferenceDate.

要解决此问题,您需要在单个值容器中对基础值进行编码,以触发任何自定义编码策略.这样做的唯一障碍是协议与自身不符,因此您不能调用容器的Encodable参数的方法(因为该参数采用<Value : Encodable>).

To fix this, you need to encode the underlying value in a single value container to trigger any custom encoding strategies. The only obstacle to doing this is the fact that protocols don't conform to themselves, so you cannot call a container's encode(_:) method with an Encodable argument (as the parameter takes a <Value : Encodable>).

解决此问题的一种方法是定义一个Encodable扩展名,以将其编码为单个值容器,然后可以在包装器中使用它:

One solution to this problem is to define an Encodable extension for encoding into a single value container, which you can then use in your wrapper:

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(_:)方法.

This takes advantage of the fact that protocol extension members have an implicit <Self : P> placeholder where P is the protocol being extended, and the implicit self argument is typed as this placeholder (long story short; it allows us to call the encode(_:) method with an Encodable conforming type).

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

Another option is to have have a generic initialiser on your wrapper that type erases by storing a closure that does the encoding:

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

在两种情况下,您现在都可以使用此包装器对异类可编码对象进行编码,同时遵守自定义编码策略:

In both cases, you can now use this wrapper to encode heterogenous encodables while respecting custom encoding strategies:

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

这篇关于JSONEncoder的dateEncodingStrategy无法正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆