How do you destructure named capture groups?

917 views Asked by At

In JavaScript, using named capture groups is pretty convenient:

const auth = 'Bearer AUTHORIZATION_TOKEN'
const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
console.log(token) // "AUTHORIZATION_TOKEN"

When I try that in typescript, it doesn't compile because groups could be null. If I put a ! after exec(...), it just kicks the can: token could be undefined.

In typescript, is there any way I can destructure the regex like above?

3

There are 3 answers

3
Bergi On BEST ANSWER

It doesn't compile because groups could be null.

No, it doesn't compile because .exec() can return null, when the regex does not match. Attempting to access a property like .groups on that will cause a TypeError: Cannot read properties of null.

You'll need a fallback value (using nullish coalescing and default initialisers) to destructure in that case:

const { groups: {token} = {} } = /Bearer (?<token>[^ $]*)/.exec(auth) ?? {}

or simpler with optional chaining:

const { token } = /Bearer (?<token>[^ $]*)/.exec(auth)?.groups ?? {}
3
Alexander Nied On

UPDATE: Bergi's answer is the more correct and typesafe way to accomplish this.


It's ugly, but something like this would work:

const auth = 'Bearer AUTHORIZATION_TOKEN'
type RegexTokenObj = { groups: { token: string } };
const { groups: { token } } = (/Bearer (?<token>[^ $]*)/.exec(auth) as unknown as RegexTokenObj)
console.log(token) // "AUTHORIZATION_TOKEN"

If you just try const { groups: { token } } = (/Bearer (?<token>[^ $]*)/.exec(auth) as RegexTokenObj) it will yell at you that "Conversion of type 'RegExpExecArray | null' to type 'RegexTokenObj' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first." Thus we can convert to unknown before casting as the type we know/expect it will be. That said, we are overriding the type system here, so we should be certain we are confident that we are correct in doing so, so be careful.

0
Wiktor Stribiżew On

You can also use a direct assignment to a token variable:

const auth = 'Bearer AUTHORIZATION_TOKEN'
const token = /Bearer (?<token>\S+)/.exec(auth)?.groups?.token
console.log(token); // => "AUTHORIZATION_TOKEN"

Since you know the group name, you can refer to the group?.token match value.

Also, note (?<token>\S+) captures one or more non-whitespace chars.