Description
See this example from a medium post on mapped types.
I have a function updateIds
which runs some subset of properties on an object through a mapping:
> updateIds({id: 'old', other: 3},
['id'],
{'old': 'new'})
{id: 'new', other: 3}
> updateIds({from: 'A', to: 'B', time: 180},
['from', 'to'],
{'A': 'A1', 'B': 'B1'})
{from: 'A1', to: 'B1', time: 180}
This function is simple to implement but not entirely straightforward to type. Here's a version using keyof
:
function updateIds<T>(
obj: T,
idFields: (keyof T)[],
idMapping: {[oldId: string]: string}
): T {
for (const idField of idFields) {
const newId = idMapping[obj[idField] as any]; // <--
if (newId) {
obj[idField] = newId as any; // <--
}
}
return obj;
}
The problem here is that obj[idField]
has a type of T[keyof T]
whereas it really needs to have a type of string
. I'd like the type system to require that T[k] = string
for all the fields passed in the array.
I don't believe there's any way to do this directly (please tell me if I'm wrong!)
One thought was to use a second type parameter for the property list:
function updateIds<K extends string, T extends {[k: K]: string}>(
obj: T,
idFields: K[],
idMapping: {[oldId: string]: string}
): T {}
But this gives the error An index signature parameter type must be 'string' or 'number'.
If the index signature parameter could instead be a subtype of string (e.g. "from" | "to"
) then I think this would work.
TypeScript Version: 2.1.4
Code
updateIds({id: /not a string/}, 'id', {'old': 'new'})
Expected behavior:
I'd like this to be rejected since the id
field doesn't have a string type.
Actual behavior:
I don't have a way to enforce this through the type system.