It seems that wrapping Date
instances in a type-erased AnyEncodable
wrapper causes the use of an incorrect date encoding strategy.
There is a workaround.
However, can someone tell me is this is a weird bug or an intended behaviour and if so what is the reason?
Given:
let date = Calendar.current.date(from: DateComponents(
year: 2020, month: 7, day: 31, hour: 11
))!
date.timeIntervalSince1970 // -> 1596186000
let encoder = JSONEncoder()
// important to not use the default deferredToDate to see the difference
encoder.dateEncodingStrategy = .secondsSince1970
Test 1 - encoding raw Date
String(data: try! encoder.encode(date), encoding: .utf8)
// correct "1596186000"
Test 2 - encoding Date
as a property in Example
struct Example: Encodable {
let date: Date
}
String(data: try! encoder.encode(Example(date: date)), encoding: .utf8)
// correct "{"date":1596186000}"
Introduce AnyEncodable
- type-erased Encodable wrapper:
struct AnyEncodable: Encodable {
private let encodable: Encodable
init(_ encodable: Encodable) {
self.encodable = encodable
}
func encode(to encoder: Encoder) throws {
try self.encodable.encode(to: encoder)
}
}
Test 3 - encoding Date
as a property in Example
wrapped in AnyEncodable
String(data: try! encoder.encode(AnyEncodable(Example(date: date))), encoding: .utf8)
// correct "{"date":1596186000}"
Test 4 - encoding raw Date
wrapped in AnyEncodable
String(data: try! encoder.encode(AnyEncodable(date)), encoding: .utf8)
// incorrect "617878800"
This test fails and uses Date
default encodable form which is timeIntervalSinceReferenceDate
.
Does it only ignores the date encoding strategy or it uses a whole new instance of encoder underneath?
Workaround:
extension Encodable {
fileprivate func encode(into container: inout SingleValueEncodingContainer) throws {
try container.encode(self)
}
}
struct AnyEncodableWorkaround: Encodable {
private let encodable: Encodable
init(_ encodable: Encodable) {
self.encodable = encodable
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try self.encodable.encode(into: &container)
}
}
Test 5 - encoding Date
as a property in Example
wrapped in AnyEncodableWorkaround
String(data: try! encoder.encode(AnyEncodableWorkaround(Example(date: date))), encoding: .utf8)
// correct "{"date":1596186000}"
Test 6 - encoding raw Date
wrapped in AnyEncodableWorkaround
String(data: try! encoder.encode(AnyEncodableWorkaround(date)), encoding: .utf8)
// correct "1596186000"
So adding this weird workaround makes both test 5 and 6 pass as oppose to test 4 from the corresponding pair of tests: 3 and 4.
Can someone tell me is this is a weird bug or an intended behaviour and if so what is the reason?
What does this single value container changes?