I have a function that takes in a rename function and an object, and renames the object's keys based on the rename function.
export default function mapKeys<T extends { [s: string]: T } | ArrayLike<T>>(
renameFunction: (key: string) => string,
object: T,
): T {
return Object.fromEntries(
Object.entries(object).map(([key, value]) => [renameFunction(key), value]),
);
}
You can use it like this:
mapKeys(key => key.toUpperCase(), { a: 1, b: 2 });
// => { A: 1, B: 2 }
The problem is, that TypeScript complains about both, the function definition:
Type '{ [k: string]: T; }' is not assignable to type 'T'.
'{ [k: string]: T; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ [s: string]: T; } | ArrayLike<T>'.
as well as the errors for the application:
Type 'number' is not assignable to type '{ a: number; b: number; }'.
and
Type 'number' is not assignable to type '{ a: number; b: number; }'.
Is there a way to type this function, so these errors compile? Ideally, TypeScript would also be aware of the output shape of the function, e.g. if the renameFunction
is something like:
const rename = (a: string) => `${a}Error`;
and the object is:
{ email: 'foo' }
that then TypeScript knows, the key in the resulting object is emailError
.
I tried writing the function more generic, like this:
export default function mapKeys(
renameFunction: (key: string) => string,
object: object,
): object {
return Object.fromEntries(
Object.entries(object).map(([key, value]) => [renameFunction(key), value]),
);
}
This compiles, but TypeScript loses the information about the object keys.
If I have an object like { name: 'foo' }
and a rename function like rename: (a: string) => a + 'Foo'
, TypeScript doesn't know the returned object is of shape { nameFoo: string }
.