3

I'm using SWXMLHash and have written an extension on NSDate for XMLElementDeserializable.

I've followed how the basic types are extended at the end of this file.

What I have looks like this:

import Foundation
import SWXMLHash

struct BlogPost: XMLIndexerDeserializable {
    let date: NSDate

    static func deserialize(blogPost: XMLIndexer) throws -> BlogPost {
        return try BlogPost(
            date: blogPost["date"].value()
        )
    }
}

extension NSDate: XMLElementDeserializable  {
    /**
     Attempts to deserialize XML element content to an NSDate

     - element: the XMLElement to be deserialized
     - throws: an XMLDeserializationError.TypeConversionFailed if the element cannot be deserialized
     - returns: the deserialized NSDate value formatted as "EEE, dd MMM yyyy HH:mm:ss zzz"
     */
    public static func deserialize(element: XMLElement) throws -> NSDate {
        guard let dateAsString = element.text else {
            throw XMLDeserializationError.NodeHasNoValue
        }

        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
        let date = dateFormatter.dateFromString(dateAsString)

        guard let validDate = date else {
            throw XMLDeserializationError.TypeConversionFailed(type: "Date", element: element)  
        }
        return validDate
    }
}

However, I'm getting an error that says:

Method 'deserialize' in non-final class 'NSDate' must return 'Self' to conform to protocol 'XMLElementDeserializable'

I've looked around S.O. for other answers to the same error and I haven't gleaned much information from them.

Any suggestions would be much appreciated. Thanks!

Xeaza
  • 1,410
  • 1
  • 17
  • 21

2 Answers2

2

Okay, this is pretty odd, but the problem exists because NSDate is a class instead of a struct. It apparently takes some work to get a class to conform to a protocol that returns self - it is far easier to get this to work with a struct!

(in Swift 4, this is unnecessary as Date is a struct)

I'll have to add some additional documentation to show how this can work.

Check out the following code (modified from your example) to see if it works for you:

import Foundation
import SWXMLHash

extension NSDate: XMLElementDeserializable  {
    // NOTE: for SWXMLHash 3.0 with Swift 3.0, this will be (_ element: XMLElement)
    public static func deserialize(element: XMLElement) throws -> Self {
        guard let dateAsString = element.text else {
            throw XMLDeserializationError.NodeHasNoValue
        }

        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
        let date = dateFormatter.dateFromString(dateAsString)

        guard let validDate = date else {
            throw XMLDeserializationError.TypeConversionFailed(type: "Date", element: element)
        }

        // NOTE THIS
        return value(validDate)
    }

    // AND THIS
    private static func value<T>(date: NSDate) -> T {
        return date as! T
    }
}

let xml = "<root><elem>Monday, 23 January 2016 12:01:12 111</elem></root>"

let parser = SWXMLHash.parse(xml)

let dt: NSDate = try! parser["root"]["elem"].value()

See also Return instancetype in Swift and Using 'self' in class extension functions in Swift.

NOTE: Added a comment noting that this will look slightly different for SWXMLHash 3.0.

David Mohundro
  • 11,922
  • 5
  • 40
  • 44
  • That does it! Thanks for your well written answer. I appreciate the extra resources at the end as well. – Xeaza Jul 05 '16 at 23:30
  • This approach does not work for me if I want to use it together with `.value(ofAttribute: "start")`. What do I need to do in this case? – funkenstrahlen Oct 07 '17 at 10:34
  • @funkenstrahlen for attribute deserialization, you'll also need to implement `XMLAttributeDeserializable`. – David Mohundro Oct 07 '17 at 23:09
0

In case someone needs that, here is the implementation for Swift 4:

extension Date: XMLElementDeserializable, XMLAttributeDeserializable {
    public static func deserialize(_ element: SWXMLHash.XMLElement) throws -> Date {
        guard !element.text.isEmpty else {
            throw XMLDeserializationError.nodeHasNoValue
        }

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = FeedParser.dateFormat
        guard let date = dateFormatter.date(from: element.text) else {
            throw XMLDeserializationError.typeConversionFailed(type: "Date", element: element)
        }
        return date
    }

    public static func deserialize(_ attribute: XMLAttribute) throws -> Date {
        guard !attribute.text.isEmpty else {
            throw XMLDeserializationError.nodeHasNoValue
        }

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = FeedParser.dateFormat
        guard let date = dateFormatter.date(from: attribute.text) else {
            throw XMLDeserializationError.attributeDeserializationFailed(type: "Date", attribute: attribute)
        }
        return date
    }
}
funkenstrahlen
  • 3,032
  • 2
  • 28
  • 40