1

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.

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.