ReactHustle

How to Sort Array of Objects In Typescript

Jasser Mark Arioste

Jasser Mark Arioste

How to Sort Array of Objects In Typescript

In this article, you'll learn how to sort an array of objects in Typescript. This can also be applied to JavaScript and node.js. After this tutorial, you'll be able to sort any array of objects in the palm of your hand.

Let's say you have an array of objects, and you want to sort by the age property:

interface User {
  id: number;
  first_name: string;
  gender: string;
  age: number;
}
const users: User[] = [
  {
    "id": 1,
    "first_name": "Abagail",
    "gender": "Female",
    "age": 1
  },
  {
    "id": 2,
    "first_name": "Roselin",
    "gender": "Female",
    "age": 88
  },
  {
    "id": 3,
    "first_name": "Lauren",
    "gender": "Male",
    "age": 36
  }
]
1234567891011121314151617181920212223242526

To sort an array of objects in typescript, you can use the Array.sort() method and pass in a compare function compareFn.

For example, to sort the list by age in ascending order:


users.sort((a,b) => {
  return a.age - b.age;
});
console.log(users) //array is now sorted by age: 1,36, 88
12345

You might wonder, what a compare function is. Let's explain it in more detail in the next section.

What is a compare function? #

The compare function is a pure function that tells javascript how to compare two values. It accepts two values, a and b which are the type of values inside the array. Since the array above is of type User[], then a and b must be of type User

By checking our VSCode editor, we can see the type of a & b which is User.

a and b are both User objects.

The compare function is expected to return an integer. And it will shift the positions of a and b depending on the result.

  1. If the result is negative, then a is less than b. This means that a will be placed before b.
  2. If the result is positive, then a is greater than b. This means that a will be placed after b.
  3. If the result is 0, then both values are equal. This means that no change in positions will occur for both a & b.

This means that if we wanted to sort by descending order, we just have to reverse the result by multiplying it by -1.

users.sort((a,b) => {
  const result = a.age - b.age;
  return result * -1;
});
console.log(users) //array is now sorted by age: 88, 36, 1
12345

Now you know how to sort an array of objects by the property of objects.

However  in the real world, you don't know at compile time (while coding) what property to use, it could be age, id, first_name or gender.  Or what the sort order would be, descending or ascending?

Let's fix this by implementing a dynamic compare function. Let's find out in the next section.

Advanced: Implementing a Dynamic compare function #

In this section, we'll create a function that creates the compare function.

To create a dynamic compare function, Let's define a function createCompareFn that returns a compare function.

function createCompareFn<T extends Object>(
  property: keyof T,
  sort_order: "asc" | "desc"
) {
  const compareFn = (a: T, b: T) => {
    const val1 = a[property];
    const val2 = b[property];
    const order = sort_order !== "desc" ? 1 : -1;
    
    switch (typeof val1) {
      case "number": {
        const valb = val2 as number;
        const result = val1 - valb;
        return result * order;
      }
      case "string": {
        const valb = val2 as string;
        const result = val1.localeCompare(valb);
        return result * order;
      }
      // add other cases like boolean, etc.
      default:
        return 0;
    }
  };
  return compareFn;
}
123456789101112131415161718192021222324252627

There's a lot going on here, but I'll explain it later.

First, let's see how we would use this function to sort the array of objects dynamically.

To use this function, we can now do this:

//sort by first_name, descending
users.sort(createCompareFn("first_name", "desc"));
console.log(users);

//sort by age, ascending
users.sort(createCompareFn("age", "asc"));
console.log(users);
1234567

The awesome thing about this is that it's fully typed and we can rely on the IntelliSense feature to know what properties are available.

Fully typed compare function

We can also define the created compare functions beforehand. We have to use a generic type parameter <User>:

const byFirstNameDesc = createCompareFn<User>("first_name", "desc")
users.sort(byFirstNameDesc);
console.log(users);
123

Depending on how you get input from the user, you can just call this function to create a compare function, and sort the data.

Let's dive deep into the explanation of this function.

Step-by-step explanation:

function createCompareFn<T extends Object>(
  property: keyof T,
  sort_order?: "asc" | "desc"
) {
  ...
}
123456

Line #1: we define a function that accepts a generic type parameter that extends Object. More info on generics.

Line #2: The first argument property is of type keyof T (Read more about on keyof operator). This means that if you have a User type, you can only pass properties that belong to the user type. That is, "first_name", "id", "age", and "gender".

Line #3: The second argument sort_order is optional. It accepts "asc" for ascending and "desc" for descending.

function createCompareFn<T extends Object>(
  property: keyof T,
  sort_order: "asc" | "desc"
) {
  const compareFn = (a: T, b: T):number => {
    ...
  };
  return compareFn;
}
123456789

Lines #5 & 8: we define & return a function compareFn that accepts 2 arguments and returns a number. This is exactly the parameter that Array.sort() needs.

Now the nitty gritty part:

function createCompareFn<T extends Object>(
  property: keyof T,
  sort_order: "asc" | "desc"
) {
  const compareFn = (a: T, b: T):number => {
    const val1 = a[property] as unknown;
    const val2 = b[property] as unkown;
    const order = sort_order !== "desc" ? 1 : -1;

    switch (typeof val1) {
      case "number": {
        const valb = val2 as number;
        const result = val1 - valb;
        return result * order;
      }
      case "string": {
        const valb = val2 as string;
        const result = val1.localeCompare(valb);
        return result * order;
      }
      // add other cases like boolean, etc.
      default:
        return 0;
    };
  }
  return compareFn;
}
123456789101112131415161718192021222324252627

Line #6: We declare val1 by accessing a[property]. We also set the type as unknown. Since at this point, we don't really know what type of primitive value it is yet.

Line #7: Same as #6.

Line #8: We get the multiplier depending on the sort_order. If it's descending ("desc"), we can use -1 to reverse the result.
Line #11-14: We use the typeof operator to check if the unknown value is a number. We compare the numbers using subtraction and multiply them by the order.

Line #15-19: We use the typeof operator to check if the unknown value is a string. We compare the strings using String.localCompare() and multiply them by the order.

Array.sort() modifies the original array #

One thing to note is that Array.sort() modifies the original array. If we want to preserve the original array while sorting, we can do so in three ways:

//using the spread operator
const sortedArray = [...users].sort(compareFn)

//using slice.
const sortedArray2 = users.slice().sort(compareFn)

//using concat
const sortedArray3 = [].concat(users).sort(compareFn)
12345678

All of these three create a shallow copy of the array before sorting.

What else can we do?  #

There are a couple of things we can do after this.

  1. Refactor the code so that the switch statement is defined in another function that compares only primitive values.
  2. Find a way to allow for nested property comparison. For example "address.street" 
  3. Complete the implementation for comparing primitive values. 

Conclusion #

We learned how to sort objects by property in typescript. We also learned how to create dynamic compare functions.

If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.

Resources #

Credits: Image by Sharon Kehl Califano from Pixabay

Share this post!

Related Posts

Disclaimer

This content may contain links to products, software and services. Please assume all such links are affiliate links which may result in my earning commissions and fees.
As an Amazon Associate, I earn from qualifying purchases. This means that whenever you buy a product on Amazon from a link on our site, we receive a small percentage of its price at no extra cost to you. This helps us continue to provide valuable content and reviews to you. Thank you for your support!
Donate to ReactHustle