I'm trying to create a function that will accept an object as a map and that a further nested function will utilize it's keys(I'll need the value as well in the future):
function exec(a: string) {
return a;
}
function one<T extends Record<string, any>>(a: T) {
return <K extends keyof T>(b: K) => {
exec(b);// TS2345: Argument of type 'string | number | symbol' is not assignable to parameter of type 'string'. Type 'number' is not assignable to type 'string'.
};
}
but I get this error TS2345: Argument of type 'string | number | symbol' is not assignable to parameter of type 'string'. Type 'number' is not assignable to type 'string'.
since object keys could be number | Symbol as well and not strictly string.
Can it be done without exec(b.toString())
?
As you noted, a string index signature does not prevent there from being non-string
keys (it only means that any string
keys that exist need to have properties compatible with the index signature, which for Record<string, any>
is exceedingly lax). And the same thing holds for generic type parameters with constraints like T extends U
; even if U
doesn't have a particular property key, it doesn't mean T
can't have that key. Essentially, object types in TypeScript are open and not exact (as requested in microsoft/TypeScript#12936), and in most circumstances the compiler will not prohibit excess properties.
This means you can call one
like this:
const sym = Symbol();
const a = { str: 1, [sym]: 2 }
const fn = one(a); // okay
fn("str"); // okay
fn(sym); // okay
which is why the implementation of one
complains that b
might not be a string
.
If you want to prevent that, you'll need to constrain K
to be not just keyof T
, but just the subtype of keyof T
also assignable to string
. The easiest way to express that is to just intersect string
with keyof T
:
function one<T extends Record<string, any>>(a: T) {
return <K extends string & keyof T>(b: K) => {
exec(b); // okay
};
}
Now the compiler knows that b
must be keyof T
as well as a string
. And now you get the desired error if you try to call the function returned from one
with a non-string
input:
const sym = Symbol();
const a = { str: 1, [sym]: 2 }
const fn = one(a); // okay
fn("str"); // okay
fn(sym); // error
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With