How can infer a type based on a key from an array of typed objects?

151 views Asked by At

I'm trying to create a type based on an array of objects using one of the recurring (and required) keys, but typescript doesn't handle Array.map() the way I expect:

const list = [
  {
    _id: 'foo1'
  },
  { _id: 'foo2' }
] as const

const listIds = list.map(listValue => listValue._id)

I expect listIds to be inferred as ['foo1', 'foo2'] but is instead treated as ("foo1" | "foo2")[]. This doesn't make sense to me since it's impossible for Array.map to return an array of a different size than the original array.

Is there another way to get the results I'm looking for?

PS. See TypeScript Playground sample here.

1

There are 1 answers

0
Lauren Yim On BEST ANSWER

For this specific case of getting the value of a specific key, a helper function can be used:

const mapByKey = <A extends readonly unknown[], K extends keyof A[number]>(
  array: A,
  key: K
// the -readonly ensures the return type isn't readonly even if the
// input array is
): {-readonly [P in keyof A]: A[P][K]} =>
  array.map((x: A[number]) => x[key]) as {-readonly [P in keyof A]: A[P][K]}

// ['foo1', 'foo2']
const listIds = mapByKey(list, '_id')

Playground link


However, I don't think there's a simple way of generalising this for any kind of function.

What we really want:

declare const map: <A extends readonly unknown[], F>(
  array: A,
  fn: <T extends A[number]>(x: T) => F<T>
): {-readonly [K in keyof A]: F<A[K]>}
// with your example F would be like a generic type like
// type F<T> = T['_id']

However, this doesn't compile as TypeScript lacks higher-kinded types. There are various hacks for emulating this in TypeScript, but they're cumbersome to use and I don't personally think it's worth it for this problem.