D. Patrick
D. Patrick

Reputation: 3022

Typescript conditional mapped types loses return type information

I'm trying to wrap my head around conditional mapped types. If I have a type defined as:

type StringyProps<T> = { [K in keyof T]: T[K] extends string ? K : never }[keyof T];

What's the [keyof T] construct at the end doing? How can find documentation about what you can do with that?

Also, I've written a function:

function getString<T>(thing: T, prop: StringyProps<T>): string {
  return thing[prop] as string;
}

If I have an interface as follows:

interface Something {
  name: string,
  id: string,
  age: number,
  weight: number
}

I get the following compile time errors:

console.log(getString(pippy, "name")); // ok
console.log(getString(pippy, "age")); // age is not assignable to parameter of type "name" | "id"

That's pretty awesome, but my function won't compile because it may be a mistake to assign the StringyProp's value to a string. If I change the return to return thing[prop] as unknown as string it returns the correct value.

How can I construct this so that I don't have to assert unknown and re-assert string?

Upvotes: 1

Views: 336

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249706

Typescript can't follow the fact that StringyProps<T> represenst only string prfoperty keys. The best solution is to use a type assertion:

function getString<T>(thing: T, prop: StringyProps<T>): string {
  return thing[prop] as unknown as string;
}

You can also change the types around a little bit, but intelisense will not suggest keys anymore so I don't really think it's worth it, call site experience is more important:

function getString<K extends PropertyKey>(thing: Record<K, string>, prop: K): string {
  return thing[prop]; // ok no assertion
}

console.log(getString(pippy, "name")); // ok
console.log(getString(pippy, "age")); //err

Upvotes: 1

Related Questions