Sorting an array of semantic versions in Typescript

Chris Engelsma
Geek Culture
Published in
4 min readSep 7, 2021
A series of colored pencils of various lengths
Photo by Jess Bailey on Unsplash

In this article, I will show you how to sort semantic version strings using a custom compare function that we can use as a callback function in the Array’s sort

What is semantic versioning?

“Semantic versioning” is a system of versioning software, whereby the version is defined by three numbers: Major release number, minor release number, and patch release number.

These values come in string form as “MAJOR.MINOR.PATCH”, so “5.2.1” means the fifth major release, second minor release, first patch. Technically, you could add more dots and add more versioning, but the hierarchy must remain the same.

Chances are you use this already — every package.json comes with one — but never knew it had a name.

Sorting an array of semantic versions.

If you find yourself having to sort using semantic version strings, (sorting a list of releases chronologically, say), we first need to split the string into its values and then compare each value. In this case we would sort by major release first, then minor, then finally by patch number.

const compareSemanticVersions = (a: string, b: string) => {

// 1. Split the strings into their parts.
const a1 = a.split('.');
const b1 = b.split('.');
// 2. Contingency in case there's a 4th or 5th version
const len = Math.min(a1.length, b1.length);
// 3. Look through each version number and compare.
for (let i = 0; i < len; i++) {
const a2 = +a1[ i ] || 0;
const b2 = +b1[ i ] || 0;

if (a2 !== b2) {
return a2 > b2 ? 1 : -1;
}
}

// 4. We hit this if the all checked versions so far are equal
//
return b1.length - a1.length;
};

We first split the strings into their respective parts, so from “1.2.3” we would get [ “1”, “2”, “3” ].

Since we can’t assume that all passed-in values have the same number of version types (e.g. “1.2.3” vs “1.2.3.4”) we loop over the values that overlap so we need the shortest length.

We then loop through each version number and compare each one.

The final return will compare the number of version labels and return the smallest one, which assumes that every “version” that’s not mentioned is a 0.

We can then use this to sort by passing it into the Array.sort method:

const versions = [ '1.0.0', '5.0.0', '0.2.0', '2.4.1', '1.0.1' ];const sorted = versions.sort(compareSemanticVersions);console.log(versions);

This prints the following result:

> [ '0.2.0', '1.0.0', '1.0.1', '2.4.1', '5.0.0' ]

If we want this sort reversed, we can just call .reverse() after the sort.

Sorting objects by a semantic version property

What if we have an array of objects that we need to sort and the semantic versions are now a property of each object? We can update our sort function to be more robust and handle this:

const compareSemanticVersions = (key?: string) => (a: any, b: any) => {    // 1. Split the strings into their parts.
let a1;
let b1;

if (key) {
a1 = a[ key ].split('.');
b1 = b[ key ].split('.');
} else {
a1 = a.split('.');
b1 = b.split('.');
}
// 2. Contingency in case there's a 4th or 5th version
const len = Math.min(a1.length, b1.length);
// 3. Look through each version number and compare.
for (let i = 0; i < len; i++) {
const a2 = +a1[ i ] || 0;
const b2 = +b1[ i ] || 0;

if (a2 !== b2) {
return a2 > b2 ? 1 : -1;
}
}

// 4. We hit this if the all checked versions so far are equal
return b1.length - a1.length;
};

The only change we made is in part 1, where now we’re checking to see if a key has been passed in, otherwise it just tries to split the value. We also added an additional parameter to our function ... = (key?: string) => (a: any, b: any) => ... so we can now pass in a key.

const objs = [ 
{ version: "1.0.0", label: "Apple" },
{ version: "5.0.0", label: "Orange" },
{ version: "0.2.0", label: "Banana" },
{ version: "2.4.1", label: "Pomegranate" },
{ version: "1.0.1", label: "Grape" }
];
const sorted = objs.sort(compareSemanticVersions('version'));console.log(sorted);

This prints the following result:

> [
{ version: '0.2.0', label: 'Banana' },
{ version: '1.0.0', label: 'Apple' },
{ version: '1.0.1', label: 'Grape' },
{ version: '2.4.1', label: 'Pomegranate' },
{ version: '5.0.0', label: 'Orange' }
]

Slap this into a module and export it so you can invoke it whenever you need it.

Happy Coding!

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Chris Engelsma
Chris Engelsma

Written by Chris Engelsma

Geophysicist, software engineer, and web developer. Hopefully if I throw enough spaghetti at the wall something sticks.

No responses yet

What are your thoughts?

Recommended from Medium

Lists

See more recommendations