Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to deal with optional Date? #61

Open
tkirby opened this issue Nov 2, 2022 · 2 comments
Open

How to deal with optional Date? #61

tkirby opened this issue Nov 2, 2022 · 2 comments

Comments

@tkirby
Copy link

tkirby commented Nov 2, 2022

Tried:

@DateValue var cancellableUntil: Date?

This gives the error:

Property type 'Date?' does not match 'wrappedValue' type 'Date'

@dimat
Copy link

dimat commented Jul 2, 2023

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

@moshegutman
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants