Frankie Frankie - 1 year ago 122
iOS Question

Swift sort array of dictionaries by key where value is optional AnyObject

I'm pulling an array of dictionaries straight from Parse and displaying them in a table. So I'd really like to work with the data structure I'm handed (the oddly structured dictionaries below).

A

PFObject
is
[String : AnyObject?]
and I want to be able to sort by any key so I don't know the object type AND the key might be missing from some of the dictionaries. Because in Parse, if you don't give a property a value, it is simply nonexistent. For example:

[
{
"ObjectId" : "1",
"Name" : "Frank",
"Age" : 32
},
{
"ObjectId" : "2",
"Name" : "Bill"
},
{
"ObjectId" : "3",
"Age" : 18
}
{
"ObjectId" : "4",
"Name" : "Susan",
"Age" : 47
}


]

I want the dictionaries with missing keys to always be ordered after the sorted dictionaries. An example:

Original Table:

ObjectId Name Age
1 Frank 32
2 Bill
3 18
4 Susan 47


Ordered By Name:

ObjectId Name Age
2 Bill
1 Frank 32
4 Susan 47
3 18


As I don't have a lot of control over the data model, and it's usage is limited throughout the application, I'd prefer to focus on an algorithmic solution rather than structural.

I came up with a way to do this but it just seems inefficient and slow, I'm certain there's someone who can do this better.

//dataModel is an array of dictionary objects used as my table source
//sort mode is NSComparisonResult ascending or descending
//propertyName is the dictionary key

//first filter out any objects that dont have this key
let filteredFirstHalf = dataModel.filter({ $0[propertyName] != nil })
let filteredSecondHalf = dataModel.filter({ $0[propertyName] == nil })

//sort the dictionaries that have the key
let sortedAndFiltered = filteredFirstHalf { some1, some2 in

if let one = some1[propertyName] as? NSDate, two = some2[propertyName] as? NSDate {
return one.compare(two) == sortMode
} else if let one = some1[propertyName] as? String, two = some2[propertyName] as? String {
return one.compare(two) == sortMode
} else if let one = some1[propertyName] as? NSNumber, two = some2[propertyName] as? NSNumber {
return one.compare(two) == sortMode
} else {
fatalError("filteredFirstHalf shouldn't be here")
}
}

//this will always put the blanks behind the sorted
dataModel = sortedAndFiltered + filteredSecondHalf


Thanks!

Answer Source

Swift can't compare any two objects. You have to cast them to a specific type first:

let arr: [[String: Any]] = [
    ["Name" : "Frank", "Age" : 32],
    ["Name" : "Bill"],
    ["Age" : 18],
    ["Name" : "Susan", "Age" : 47]
]

let key = "Name" // The key you want to sort by

let result = arr.sort {
    switch ($0[key], $1[key]) {
        case (nil, nil), (_, nil):
            return true
        case (nil, _):
            return false
        case let (lhs as String, rhs as String):
            return lhs < rhs
        case let (lhs as Int, rhs as Int):
            return  lhs < rhs
        // Add more for Double, Date, etc.
        default:
            return true
    }
}

print(result)

If there are multiple dictionaries that have no value for the specified key, they will be placed at the end of the result array but their relative orders are uncertain.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download