Search code examples
iosswiftswxmlhash

How to deserialize NSDate with SWXMLHash


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!


Solution

  • 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.