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.