I have a union of a bunch of different strings, which represent all the translation keys supported by the application. They are written in a standardized form, with '.'
's as separators. For example:
type Keys = 'foo.bar' | 'baz.bar' | 'fail.error';
I would like to derive from that union another union, which contains all the root strings that end in .bar
. So for this example, the desired output is 'foo' | 'baz'
My best solution so far is as follows, but it requires me to always explicitly state the string as a parameter to the generic:
type Namespace<T extends string = string> = `${T}.bar` extends Keys ? T : never;
const example0: Namespace<'foo'> = 'foo'; // Allowed, but i had to explicitly give the type
const example1: Namespace<'notFound'> = 'notFound'; // Correctly gives an error
I would like to be able to do the following, but my current version results in an error on each line:
const example2: Namespace = 'foo'; // Should be allowed, but isn't
const example3: Namespace = 'baz'; // Should be allowed, but isn't
const example4: Namespace = 'fail'; // Should be an error
const example5: Namespace = 'notFound'; // Should be an error
Is there a better way i can do this type?
CodePudding user response:
My approach here would be to create a distributive object type (as coined in microsoft/TypeScript#47109) where we first create a mapped type over Keys
where each key P
has a property that is the first part of P
if it ends in ".bar"
, or never
otherwise. And then we immediately index into this object type with Keys
to get the Namespace
union type we want. This serves to distribute the operation of extracting the part before ".bar"
over the union of entries in Keys
:
type Namespace = { [P in Keys]: P extends `${infer K}.bar` ? K : never }[Keys];
// type Namespace = "foo" | "baz"
This could be generalized if necessary to Prefix<T, U>
which takes a union of string literals T
and a suffix U
and returns the prefix of the elements of T
that are followed by the suffix U
:
type Prefix<T extends string, U extends string> =
{ [P in T]: P extends `${infer K}${U}` ? K : never }[T];
type AlsoNamespace = Prefix<Keys, ".bar">
// type AlsoNamespace = "foo" | "baz"