Home > other >  is it possible to use value of some key as new key in typescript?
is it possible to use value of some key as new key in typescript?

Time:05-31

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 symbols, 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

Playground Link

  • Related