Using Swift 2.1 I'm trying to make a function that sorts an array of dictionaries by the date value for the key dateKey.
I want to add this as an extension to the Array type, so I can call it using someArray.sortDictionariesByDate(dateKey: String, dateFormatter: NSDateFormatter)
extension Array where Element: CollectionType {
mutating func sortDictionariesByDate(dateKey: String, dateFormatter: NSDateFormatter) {
sortInPlace {
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
if let dateStringA: String = ($0 as? [String: AnyObject])?[dateKey] as? String, let dateStringB = ($1 as? [String: AnyObject])?[dateKey] as? String {
if let dateA: NSDate = dateFormatter.dateFromString(dateStringA), dateB: NSDate = dateFormatter.dateFromString(dateStringB) {
return dateA.compare(dateB) == .OrderedAscending
}
}
return false
}
}
}
This works fine as long as the dictionaries are typed as [String: AnyObject]
, but if I use it on a dictionary of type [String: String]
it doesn't work since it's unable to cast String
to AnyObject
. I assume this is because String
is a Struct
not a Class
. I've also tried to typecast the elements to [String: Any]
instead, but then it won't work regardless of using dictionaries of type [String: AnyObject]
or [String: String]
.
Is there any cast I can use to support dictionaries with key type String
and any value type (String
, AnyObject
etc.), or perhaps a where clause or protocol conformance that can be added to the extension to avoid casting completely?
EDIT: Here are two example arrays as per request
var sampleArray1: [[String: AnyObject]] = [["date": "2015-10-24T13:00:00.000Z", "foo": "bar"], ["date": "2015-10-24T14:00:00.000Z", "foo": "bar"]]
sampleArray1.sortDictionariesByDate("date", dateFormatter: NSDateFormatter())
var sampleArray2: [[String: String]] = [["date": "2015-10-24T13:00:00.000Z", "foo": "bar"], ["date": "2015-10-24T14:00:00.000Z", "foo": "bar"]]
sampleArray2.sortDictionariesByDate("date", dateFormatter: NSDateFormatter())
First issue: you are comparing strings, not dates. Fortunately, your string's format make it directly comparable as both a string and the date value it represents. Hence, you don't need to convert it to NSDate
at all.
The second issue is that typecasting Dictionary<Key,Value1>
to Dictionary<Key,Value2>
wholesale doesn't work, even when Value1
is covariant on Value2
. It may work in trivial example like this...
let a : [String: String] = ["name": "David", "location": "Chicago"]
let b = a as [String: AnyObject]
...because the compiler can see the value type (String
) in a
and optimize it by compile time. For dynamic situations like yours, there's no information available ahead of time.
When you need dynamism, you can always go back to the old friend, Objective-C:
extension Array where Element: CollectionType {
mutating func sortDictionariesByDate(dateKey: String) {
self.sortInPlace {
if let a = $0 as? NSDictionary, b = $1 as? NSDictionary {
return (a[dateKey] as? String) < (b[dateKey] as? String)
} else {
return false
}
}
}
}
// Example:
var sampleArray1: [[String: AnyObject]] = [
["date": "2015-10-24T13:00:00.000Z", "foo": "bar"],
["date": "2015-10-24T14:00:00.000Z", "foo": "bar"],
["date": "2015-10-24T10:00:00.000Z", "foo": "bar"]
]
var sampleArray2: [[String: String]] = [
["date": "2015-10-24T13:00:00.000Z", "foo": "bar"],
["date": "2015-10-24T14:00:00.000Z", "foo": "bar"],
["date": "2015-10-24T10:00:00.000Z", "foo": "bar"]
]
sampleArray1.sortDictionariesByDate("date")
sampleArray2.sortDictionariesByDate("date")
Note that since you are now comparing strings rather than date, no NSDateFormatter
is needed.