BlogCode

Regex groups with .replace() in Javascript

Written by Codemzy on February 2nd, 2022

Regex groups are super useful when you need to replace specific patterns in strings. In this blog post, discover how to use multiple groups to perform simple (and complex) replacements.

One of the places I find regex groups most useful is when using regular expressions for replacement.

In the last blog post, we looked at [how to create and use groups in regex matches](regex groups). And since the .replace() function can also use regex, you will create groups in the same way.

But how you can use those groups with .replace() is super cool.

Let’s look at a real example to show how useful regex groups can be.

In this first example, you have a list of names, maybe from an address book or some other service. The list has over 1000 names, and it looks like this:

Johnson, Mike
Smith, Sue
Timpson, Lisa
Rogers, Frank
Thomson, Dawn
Daniels, Peter
Griffin, John
Bridge, Mark Sam
…

Each name is on a new line, and the pattern is [lastname], [firstname]. But you need the names presented differently. You want [firstname] [lastname].

It would take a long time to go through each name and edit it manually. But if you know regex (and regex groups) you can automate the transformation. In seconds!

Accessing the group in a replace is similar to what we did in the last blog post match, but instead of result[1] you can use $1 (or if it's the second group $2).

And this also works with the g global flag without having to loop over an object or an array (like we had to when performing a match).

I love using regex groups with .replace(), because $1, $2 etc tokens are easy to remember and you can transform long strings quickly.

let result = theString.replace(/(\w+), (\w+)/gm, "$2 $1");
console.log(result); // Mike Johnson...

Let's break that down.

The regex here is /\w+, \w+/gm with \w matching any word character and + matching between one and unlimited times (greedy). And the , literally matches the comma. The gm flags make the match g global (multiple matches) since we want to match many names and m multi-line since our string is across multiple lines.

Then for the groups, we wrap the two name parts of the match in parenthesis /(\w+), (\w+)/gm so that we can access those parts of the match in the replacement.

The replacement "$2 $1" simply means, return the second group, a space, then the first group.

Accessing groups this way is great until you have a lot of them. If you start to struggle to know what order they are in, there’s a better way!

Naming regex groups

Forget $1 and $2, you can name your groups something more memorable instead.

(Don’t actually forget $1 and $2, it’s still the quickest way to create and access groups if you just have a few!)

To name a group, you give it a name when you create it. Instead of (\w+), you can add a name with ?<groupName> at the start of the group, like (?\w+).

In a replace, you can insert the group by its name like this $<groupName>.

Going back to our name replacement, let’s name the groups.

let result = theString.replace(/(?<lastName>\w+), (?<firstName>\w+)/gm, "$<firstName> $<lastName>");
console.log(result); // Mike Johnson...

Using groups with the replacer function

Instead of passing a new string directly to replace() like we just did, you can also give it a function.

This gives you more power with your replacement and your groups.

Let’s say in your address book string, some people have middle names. And it’s messing up your code!

Currently, those middle names are being missed, and your new address book is incorrect. Oh no!

Now you create an optional* third group to collect those middle names, and that would work, but in our replacement strings it would mess up the spacing. Now names without a middle name will have an extra space between the first and last name.

*To create an option group you can add a ? after the group. Note that I've also added a question mark before the new middleName group, and that's because only people with a middle name have a space after their first name.

let result = theString.replace(/(?<lastName>\w+), (?<firstName>\w+) ?(?<middleName>\w+)?/gm, "$<firstName> $<middleName> $<lastName>");
console.log(result); 
// Mike  Johnson
// …
// Mark Sam Bridge

It’s times like this when you need conditional logic or slightly more complex replacements, that the replacer function is needed. Instead of passing a replacement string, we can use a more powerful function instead.

Here’s what that function looks like.

function(match, g1, g2, g3, offset, string, groups) {
 // return the new pattern
};

The number of arguments the function receives depends on the number of groups. The replacer function gets 3 because there are 3 groups (g1, g2, g3), but in the real world, I would name them based on what the group is capturing to make the code more readable. Let’s do that.

let result = theString.replace(/(?<lastName>\w+), (?<firstName>\w+) ?(?<middleName>\w+)?/gm, function(match, lastName, firstName, middleName) {
    return firstName + " " + (middleName ? middleName + " " : "") + lastName;
});
console.log(result); 
// Mike Johnson
// …
// Mark Sam Bridge

If you know template literals then you can get that replacer function looking much nicer too.

let result = theString.replace(/(?<lastName>\w+), (?<firstName>\w+) ?(?<middleName>\w+)?/gm, function(match, lastName, firstName, middleName) {
    return `${firstName} ${middleName ? `${middleName} ` : ""}${lastName} `;
});
console.log(result); 
// Mike Johnson
// …
// Mark Sam Bridge

That's a fairly simple example, but the replacer function allows you to get pretty complex with your string transformations.