Reputation: 19238
type Data = {
arrayObject: {
firstName: string;
lastName: string;
}[];
};
type ArrayElementType<A> = A extends readonly (infer T)[] ? T : never
type TupleOf<A extends number, R extends number[] = []> =
R['length'] extends A ? R : TupleOf<A, [...R, R['length']]>;
type Integers = TupleOf<1>;
type Indexes = Integers[number];
type PathFinder<T, Key extends keyof T = keyof T> = Key extends string
? T[Key] extends (string | boolean | number | symbol)[]
? `${Key}.${Indexes}`
: T[Key] extends object[]
? `${Key}.${Indexes}.${PathFinder<ArrayElementType<T[Key]>>}`
: T[Key] extends Record<string, any>
? `${Key}.${PathFinder<T[Key]>}`
: Key
: never;
function register<Data>(name: PathFinder<Data>) {
console.log(name)
}
const index = '0'
register(`arrayObject.${index}.lastName`); // break with variable
register(`arrayObject.0.firstName`);
register(`arrayObject.0.firstNa`);
Is it possible to differentiate string and template string literal? The problem that I am facing is when a template string literal contains a variable, it will convert to string
.
The solution so far is below: however, it's not ideal for our API, as register
function doesn't expose to Data
type.
type Test1<TName extends string> = string extends TName ? string : PathFinder<Data>;
function register<TName extends string>(name: Test1<TName>) {
console.log(name)
}
Upvotes: 0
Views: 78
Reputation: 327624
If I understand correctly, your problem is that in TypeScript 4.1, the template literal value `arrayObject.${index}.lastName`
is inferred to have a type of string
instead of the desired string literal type "arrayObject.0.lastName"
:
const index = '0';
// TS 4.1
const lit = `arrayObject.${index}.lastName`;
// const lit: string 🙁
You can address this by using a const
assertion, which tells the compiler to interpret such values as string literals (as implemented in microsoft/TypeScript#40707):
// TS 4.1
const litAsConst = `arrayObject.${index}.lastName` as const;
// const litAsConst: "arrayObject.0.lastName" 😊
So for your code in the Playground, you could write it like this:
register<Data>(`arrayObject.${index}.lastName` as const); // okay
Even better, it looks like TypeScript 4.2 will infer such literal types automatically in places where they are expected (such as declaring the variable as a const
), as implemented in microsoft/TypeScript#41891 Which means that as const
will no longer be necessary:
// TS 4.2
const lit = `arrayObject.${index}.lastName`;
// const lit: "arrayObject.0.lastName" 😊
and your original code will work with no changes:
register<Data>(`arrayObject.${index}.lastName`); // okay
Playground link to TS4.2-dev code
Upvotes: 2