Sorting an Array of Dictionaries

How would sort an array of dictionaries in Objective-C? Now how would you do it in Swift?

Last updated: Jan 17, 2020

Let’s suppose we have a dictionary object with three keys (“surname”, “given” and “title”) to represent the contact details for a person. The question is how would we sort an array of such dictionary items. For illustration, consider the following array of dictionaries. We need to sort first on the value of the “surname” key and then on the value of the “given” key:

NSArray *people = @[
  @{@"surname":@"Simpson", @"given":@"Homer", @"title":@"Mr"},
  @{@"surname":@"Simpson", @"given":@"Marge", @"title":@"Mrs"},
  @{@"surname":@"Simpson", @"given":@"Bart", @"title":@"Mr"},
  @{@"surname":@"Simpson", @"given":@"Lisa", @"title":@"Miss"},
  @{@"surname":@"Simpson", @"given":@"Maggie", @"title":@"Miss"},
  @{@"surname":@"Flanders", @"given":@"Ned", @"title":@"Mr"}
];

That is easily achieved with two sort descriptors as follows:

NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc]
 initWithKey:@"surname"  ascending:YES];
NSSortDescriptor *givenDescriptor = [[NSSortDescriptor alloc]
 initWithKey:@"given"  ascending:YES];
NSArray *sortDescriptors = @[nameDescriptor, givenDescriptor];

Sorting the array of dictionaries is then trivial:

NSArray *ordered = [people sortedArrayUsingDescriptors:sortDescriptors];

So What About Swift?

I still don’t find it easy to move between Objective-C and Swift code. To get some practise I wondered how the above would be implemented in Swift?

First the easy part of constructing the array of dictionaries which is, I think, a little cleaner than the Objective-C syntax:

let family = [
  ["surname":"Simpson", "given":"Homer", "title": "Mr"],
  ["surname":"Simpson", "given":"Marge", "title": "Mrs"],
  ["surname":"Simpson", "given":"Bart", "title": "Mr"],
  ["surname":"Simpson", "given":"Lisa", "title": "Miss"],
  ["surname":"Simpson", "given":"Maggie", "title": "Miss"],
  ["surname":"Flanders", "given":"Ned", "title": "Mr"]
]

(I should say here that for the purposes of this discussion I am ignoring the fact that there are better ways to represent this data)

To sort a Swift array I want to use the Swift standard library rather than falling back on the more familiar foundation classes. Sorting an array of strings in Swift is very simple and obvious:

let names = ["Homer","Marge","Bart","Lisa","Maggie"]
let ordered = names.sorted(by: <)
// [Bart, Homer, Lisa, Maggie, Marge]

The power of the Swift type system and the ability to infer so much from types leads to some very terse code. To sort an array of String values we need a comparison closure that takes two String arguments and returns a Bool:

(String, String) -> Bool

In the most verbose form you can even write this as a standalone function as follows:

func compareNames(s1:String, s2:String) -> Bool {
    return s1 < s2
}
let ordered = names.sorted(by: compareNames)

We can move that function to an inline closure expression and infer the types of the two arguments and the return value and use $0, $1 as shorthand for the s1, s1 arguments:

let ordered = names.sorted(by: {$0 < $1} )

Since the String type implements the < operator which is just a function that takes two String values and returns a Bool you end up at the more readable statement:

let ordered = names.sorted(by: <)

So to sort our array of dictionaries we need to start with a function that compares two dictionaries containing our person keys and values (both of type String):

func personSort(p1:[String:String], p2:[String:String]) -> Bool {
  guard let s1 = p1["surname"], let s2 = p2["surname"] else {
    return false
  }

  if s1 == s2 {
    guard let g1 = p1["given"], let g2 = p2["given"] else {
      return false
    }
    return g1 < g2
  }

  return s1 < s2
}

Update 6-Jan-2017: I should note that with the introduction of Swift 3 the above code had to be changed as we can no longer directly compare two optionals values like this:

if p1["surname"] < p2["surname] {...}

Instead we guard against the case that the dictionary does not contain the key and only compare non-nil values.

Comparing to the Objective-C code this is equivalent to creating the sort descriptors. We first compare the surnames and if equal also compare the given names. The verbose way to sort our array of dictionaries is then as follows:

let ordered = family.sorted { personSort(p1:$0, p2:$1) }

After inlining the function and removing what can be inferred I ended up with the following:

let ordered = family.sorted {
  guard let s1 = $0["surname"], let s2 = $1["surname"] else {
    return false
  }

  if s1 == s2 {
    guard let g1 = $0["given"], let g2 = $1["given"] else {
      return false
    }
    return g1 < g2
  }

  return s1 < s2
}

Let me know if you have a better way.