iosarraysswiftsortingpfobject

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!


Solution

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