Bill
Bill

Reputation: 19238

is there any way to differentiate string and template string literal

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)
}

Playground

Upvotes: 0

Views: 78

Answers (1)

jcalz
jcalz

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.1 code

Playground link to TS4.2-dev code

Upvotes: 2

Related Questions