How to Sort Array of Objects In Typescript
Jasser Mark Arioste
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
.
The compare function is expected to return an integer. And it will shift the positions of a
and b
depending on the result.
- If the result is negative, then a is less than b. This means that a will be placed before b.
- If the result is positive, then a is greater than b. This means that a will be placed after b.
- 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.
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.
- Refactor the code so that the switch statement is defined in another function that compares only primitive values.
- Find a way to allow for nested property comparison. For example
"address.street"
- 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 #
- Array.sort() docs
- Typescript docs
- Recommended resource about Typescript.
Credits: Image by Sharon Kehl Califano from Pixabay