utahiosmac / Marshal

Marshaling the typeless wild west of [String: Any]
MIT License
698 stars 63 forks source link

Expected type Date for key... #101

Closed pepasflo closed 7 years ago

pepasflo commented 7 years ago

Sometimes I get into a situation where Date parsing doesn't seem to work.

This:

self.createdAt = try json.value(for: "created_at")

Throws this:

Type mismatch. Expected type Date for key: created_at. Got '__NSCFString'

I can work around this by transforming to a string first, then to Date:

let s: String = try json.value(for: "created_at")
self.createdAt = try Date.value(for: s)

My Date support looks like so:

extension Date {
    @nonobjc static let ISO8601SecondFormatter:DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ";
        formatter.timeZone = TimeZone(abbreviation:"GMT")
        return formatter
    }()

    static func fromISO8601String(_ dateString:String) -> Date? {
        if let date = ISO8601SecondFormatter.date(from: dateString) {
            return date
        }
        return .none
    }
}
extension Date : ValueType {
    public static func value(_ object: Any) throws -> Date {
        guard let dateString = object as? String else {
            throw MarshalError.typeMismatch(expected: String.self, actual: type(of: object))
        }
        guard let date = Date.fromISO8601String(dateString) else {
            throw MarshalError.typeMismatch(expected: "ISO8601 date string", actual: dateString)
        }
        return date
    }
}
bwhiteley commented 7 years ago

@pepasflo Is it because your signature is a bit off? Try static func value(from object: Any) throws -> Value

pepasflo commented 7 years ago

@bwhiteley That was precisely it!

For those following along at home, this causes an exception:

extension Date : ValueType {
    public static func value(_ object: Any) throws -> Date {

while this works correctly:

extension Date : ValueType {
    public static func value(from object: Any) throws -> Date {

(the only difference being _ in place of from).

It is a shame I can't lean on the compiler to spot this difference. It turns out you can make any sort of typo in the function name, or even omit it entirely, and Xcode will compile it without complaining that Date doesn't adhere to ValueType:

extension Date : ValueType {}

Why is it that I can get away with declaring an empty protocol conformance? Is this because one of the other generic implementations of ValueType will satisfy the value(from:) requirement?

pepasflo commented 7 years ago

(marking closed)

bwhiteley commented 7 years ago

@pepasflo It is because ValueType has a protocol extension providing a default implementation.

I've been bit by this a sort of thing few times. It can happen with ObjC protocols that have optional methods, or Swift protocols that have default implementations in a protocol extension. If you have a typo in your func name, it's not conforming to the protocol like you expect but the compiler offers no help. I've wished Swift had a conforming keyword for protocol conformance that behaved like override so the compiler could catch such things.

Since your first message I've been reading swift-evolution discussions about such a thing. It appears there are edge cases that complicate adding such a feature.

pepasflo commented 7 years ago

Thanks so much for taking to time to explain this!