BetterCodable
BetterCodable copied to clipboard
How to deal with optional Date?
Tried:
@DateValue<ISO8601Strategy> var cancellableUntil: Date?
This gives the error:
Property type 'Date?' does not match 'wrappedValue' type 'Date'
You can add it to your own project where you use BetterCodable.
import Foundation
import BetterCodable
@propertyWrapper
public struct OptionalDateValue<Formatter: DateValueCodableStrategy> {
private let value: Formatter.RawValue?
public var wrappedValue: Date?
public init(wrappedValue: Date?) {
self.wrappedValue = wrappedValue
if let value = wrappedValue {
self.value = Formatter.encode(value)
} else {
self.value = nil
}
}
}
extension OptionalDateValue: Decodable where Formatter.RawValue: Decodable {
public init(from decoder: Decoder) throws {
self.value = try Formatter.RawValue?(from: decoder)
if let value = value {
self.wrappedValue = try Formatter.decode(value)
} else {
self.wrappedValue = nil
}
}
}
extension OptionalDateValue: Encodable where Formatter.RawValue: Encodable {
public func encode(to encoder: Encoder) throws {
try value.encode(to: encoder)
}
}
extension KeyedDecodingContainer {
func decode<T>(_ type: OptionalDateValue<T>.Type, forKey key: Self.Key) throws -> OptionalDateValue<T>
where T.RawValue: Decodable
{
try decodeIfPresent(type, forKey: key) ?? OptionalDateValue<T>(wrappedValue: nil)
}
func decodeIfPresent<T>(_ type: OptionalDateValue<T>.Type, forKey key: Self.Key) throws -> OptionalDateValue<T>
where T.RawValue == String
{
let stringOptionalValue = try decodeIfPresent(String.self, forKey: key)
guard let stringValue = stringOptionalValue else {
return .init(wrappedValue: nil)
}
let dateValue = try T.decode(stringValue)
return .init(wrappedValue: dateValue)
}
}
And here are some tests:
import XCTest
@testable import YOUR_PROJECT
import BetterCodable
class OptionalDateValueTests: XCTestCase {
struct TestData: Codable {
@OptionalDateValue<ISO8601WithFractionalSecondsStrategy>
var date: Date?
}
func testDecodingTimestamp() {
let jsonData = #"{"date": "1984-11-09T00:00:00.000Z"}"#.data(using: .utf8)!
do {
let data = try JSONDecoder().decode(TestData.self, from: jsonData)
print(data)
} catch {
XCTFail("shouldn't throw: \(error)")
}
}
func testDecodingNil() {
let jsonData = #"{"date": null}"#.data(using: .utf8)!
do {
let data = try JSONDecoder().decode(TestData.self, from: jsonData)
XCTAssertNil(data.date)
} catch {
XCTFail("shouldn't throw: \(error)")
}
}
func testDecodingNotPresent() {
let jsonData = #"{}"#.data(using: .utf8)!
do {
let data = try JSONDecoder().decode(TestData.self, from: jsonData)
XCTAssertNil(data.date)
} catch {
XCTFail("shouldn't throw: \(error)")
}
}
}
I believe there is a bug. The Encodable
extension on OptionalDateValue
should try to encode the wrappedValue
instead of value
extension OptionalDateValue: Encodable where Formatter.RawValue: Encodable {
public func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
}