6

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?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Daniel Kaplan
  • 62,768
  • 50
  • 234
  • 356

3 Answers3

5

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 ?? {}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks! I'm a little confused how this works as a whole. `/Bearer (?[^ $]*)/.exec(auth)?.groups` I think this is saying "return `groups` if it exists, otherwise return `undefined`". The `?? {}` says, "if the result is nullish, return an empty object." But on the left-hand side, you deconstruct that (potentially) empty object to `{ token }`, and when I hover over `token` in the ts playground, it says it's of type `string`. How does the compiler know that, and why isn't that an error, "`token` does not exist on type {}"? – Daniel Kaplan Apr 23 '22 at 22:02
  • The compiler knows that `groups` is `Record` (or `{[group: string]: string}`). The `.token` might or might not exist in that (the compiler doesn't know the regex groups), but if it does, it's `string`. And `{}` is inferred to be of the same type by context, it's not empty - though it should, and `token` really needs type `string | undefined`. I suppose only `const token = /…/.exec(auth)?.groups?.token` does that. – Bergi Apr 23 '22 at 22:10
  • Sorry, I'm kind of feeling around in the dark here because I don't know what I don't know, but maybe this is the answer to my own question: since `groups` is type `{ [key: string]: string; } | undefined` but the `?? {}` infers the type as `{ [key: string]: string; }`. This type doesn't allow for the possibility of a missing `key` or value, so anything on the left hand side is assumed to be a `string`? Am I understanding that correctly? I still don't get why `?? {}` is inferred to be the same type as `group` instead of a `{}` type. – Daniel Kaplan Apr 23 '22 at 22:30
1

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.

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
0

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.

Alexander Nied
  • 12,804
  • 4
  • 25
  • 45
  • Funny. I've never used `type` in a function before. If I wanted to be more typesafe, what would that code look like? – Daniel Kaplan Apr 23 '22 at 01:07
  • I used `type` more as a visual helper-- you could define it outside, or just remove the abstraction and replace the type directly in the line assigning `token`. As for how to do it more typesafe... I'm not sure, honestly-- perhaps another user may have a better, safer approach than this that they can share as an answer... – Alexander Nied Apr 23 '22 at 01:17
  • 2
    [Bergi's answer](https://stackoverflow.com/a/71980477/6831341) is the more correct; typesafe way to accomplish this. – Alexander Nied Apr 23 '22 at 18:40