Typescript select string properties

Viewed 201

I'm trying to figure out how to correctly type a show function that would take an object T and a key K for which T[K] can be guaranteed to have a toString() method implemented.

Here is my attempt using mapped types

type ToStringablePropertyKeys<T> = keyof {
    [K in keyof T]: { toString(): string }
}

function show<T, K extends ToStringablePropertyKeys<T>>(t: T, k: K): string {
    return t[k].toString()
}

But the compiler says Property 'toString' does not exist on type 'T[K]'.

What am I missing here? How do I convince tsc that toString is in fact there by definition of K?

2 Answers

An alternative approach:

function show<K extends string, T extends {[key in K]:{toString(): string}}>
(t: T, k: K ): string {
return t[k].toString()
}

Here's an almost ideal solution. Though typescript can enforce the keys to be only the ones that expand on {toString():string}, it's not smart enough to infer that from there on, all T[k] will have toString():string...

type ToStringable = { toString(): string };

type ToStringablePropertyKeys<T> = {
    [k in keyof T]: T[k] extends ToStringable ? k : never
}[keyof T];

function show<T, K extends ToStringablePropertyKeys<T>>(t: T, k: K): string {
    return (t[k] as any).toString(); // it seems typescript is not smart enough to infer that all t[k] will have toString
}

const testObject = {
    string: 'test',
    number: 123,
    null: null,
    undefined: undefined,
};

show(testObject, 'number');
show(testObject, 'string');
show(testObject, 'invalid-prop'); //this fails
show(testObject, 'null'); //this fails
show(testObject, 'undefined'); //this fails

https://www.typescriptlang.org/play?ts=4.3.4#code/C4TwDgpgBAKg9gZWAJwJYDsDmBDARgG2gF4oBvKYRFDTACgEoAuKAZ2qygF8BuAKF9CRYVNFjyEACsjiRkoANIQQLADwwAfFBKleUPVADaAaygYoRpXABmsALrMYx21AgAPYBHQATFsKSicAmgAfnMoZnQIADcIZF5OY0sbGFs+XisAV3QAY2BUOHRWAAs4AHc1ABooeRd3Tx8-dkDJaVkFJVUNdVpgByqjZnkmViayXX1kCGAM5EKepyhsX2x0EHoAOkp-GgZuKAB6fdNgVggIAFtfQQgWbLQwE9RfdDgTlnPsORcXjMwiijgpnQVliFCK2BO2Hw+AoC1KqGhUHBMQB2yw8X42QKbAoN2AAHlcAArCC5LRjfQjALMADkHjYNIq4z06Ay51wsWYAEYAEwAZiZlNZ0IiGWhgv0WS8ECsGAgXmYUplcq8TJ4-BYJVKPTxhJJuSqNNZ7NiNPofE1ZR1bD1pOAhrYATNFq11oJxLthowUShqC8AFowK1nQd9sAik8oFZsAiWLxLdr6e79faoEaxfgQ4dw5Ho7H466k7aDWmlbLIl4s2GI748-g40A

Related