Home > OS >  Infer type from array of objects
Infer type from array of objects

Time:01-11

I have an array of objects:

const input = [
    { id: "a" },
    { id: 5 }
]

I would like to create function, that will return id property from one of those objects in the array and I need to have it exact type from the input.

interface Data<Id> {
    id: Id
}

const getFirstId = <Id>(data: ReadonlyArray<Data<Id>>): Id => {
    return data[0].id
}

const firstId = getFirstId(input) // firstId should be "a" | 5.

My attempt above however works only if all ids are strings (eg. "a" | "b") or only numbers (eg. 1 | 2), but if I combine more types together, the function doesn't want to accept input argument with an error:

Type 'number' is not assignable to type '"a"'.

How can I fix it? Here is an example.

CodePudding user response:

In order to infer it you can use variadic tuple types

const input = [
  { id: "a" },
  { id: 5 }
] as const

interface Data<Id> {
  id: Id
}

const getFirstId = <
  Id,
  Tuple extends Array<Data<Id>>
>(data: readonly [...Tuple]): [...Tuple][number]['id'] => {
  return data[0].id
}

const firstId = getFirstId(input) // firstId should be "a" | 5.

Also, please take into account that you either need to use as const assertion with your array or provide literal array as an argument, like here:

const input = [
  { id: "a" },
  { id: 5 }
]

interface Data<Id> {
  id: Id
}

const getFirstId = <
  Id extends string | number,
  Tuple extends Array<Data<Id>>
>(data: [...Tuple]): [...Tuple][number]['id'] => {
  return data[0].id
}

const firstId2 = getFirstId([
  { id: "a" },
  { id: 5 }
]) // firstId should be "a" | 5.

CodePudding user response:

Adding another generic for the type of Id, and a helper to infer the type explicitly gives the result you're after:

interface Data<Id> {
    id: Id
}

type InferIdKey<I extends ReadonlyArray<Data<string | number>>> = I extends ReadonlyArray<Data<infer Id>> ? Id : unknown;

const getFirstId = <D extends ReadonlyArray<Data<V>>, V extends string | number>(data: D) => {
    return data[0].id as InferIdKey<D>
}

const firstId = getFirstId([
    { id: "a" },
    { id: 5 }
])

There may be a simpler way to express this, but I couldn't seem to find one.

  • Related