say i have an object list, and each object is type of IUser
type IUser = {
id: string
name: string
...
}
I want to convert this list to a map, use id as key(or other property from object).
And hope I can have an accurate typescript description for this type, so that I can use it like this const idMap :IMapKeyedByK<IUser, 'id'> = {...}
type IMapKeyedByK<T, K> = {
[key: T[K]]: T
}
How can I implement IMapKeyedByK<T, K>?
CodePudding user response:
Note that JavaScript object index types are always strings or symbols. You can index an object using numbers, but they get converted to strings: ({42: 'foo'})['42'] == 'foo'
. So unless your IUser
contains symbol
s, this is not very useful because you might as well hardcode string
keys.
That said, you can do it. We have to constrain K
to be a valid key type, and constrain T
so that it contains a key of type K
with a value that can also be used as a key.
type ObjectKey = string | number | symbol
type IMapKeyedByK<T extends Record<K, ObjectKey>, K extends ObjectKey> = Record<T[K], T>
const idMap: IMapKeyedByK<IUser, 'id'> = {}
idMap['zaphod'] = {id: 'zaphod', name: 'Zaphod Beeblebrox'} // OK
idMap[Symbol()] = {id: 'zaphod', name: 'Zaphod Beeblebrox'} // Error! Type 'symbol' cannot be used as an index type.
CodePudding user response:
A correct type here is:
ype IMapKeyedByK<T, K extends keyof T> = T[K] extends PropertyKey // if the property value can be used as key
? Record<T[K], T> // record indexed by the property value
: never;
It will ensure that you pass a key belonging to T
and also that the value for that key is valid to use as an object key.
Example:
type IUser = {
id: string
name: string
age: number
department: {
departmentName: string;
}
}
type IMapKeyedByK<T, K extends keyof T> = T[K] extends PropertyKey // if the property value can be used as key
? Record<T[K], T> // record indexed by the property value
: never; // or not possible to index
type IMapKeyedByK<T, K extends keyof T> = T[K] extends PropertyKey ? Record<T[K], T> : never;
type MapById = IMapKeyedByK<IUser, "id">; //works - indexed by string
type MapByName = IMapKeyedByK<IUser, "name">; //works - indexed by string
type MapByAge = IMapKeyedByK<IUser, "age">; //works - indexed by number
type MapByDepartment = IMapKeyedByK<IUser, "department">; //produces `never` because cannot be indexed by object
type MapByDepartment = IMapKeyedByK<IUser, "phone">; // error - "phone" does not exist