Typescript value of 2nd item of array based on value of 1st item array

Viewed 332

I have object of arrays, where each array is pair [value, function], where function accepts value in parameter:

const items = {
    first: ['a', val => val.length], // val should be string
    second: [[1, 2], val => val.push(3)] // val should be number[]
}

Is there any way to set the type of function parameter (second array item) the same as the type of first array item? I created this, but it's not working (a type of function parameter is always unknown):

type Items = {
    [key: string]: any extends [infer Value, any] ? [Value, (val: Value) => any] : never
}

const items: Items = {
    first: ['a', val => val.length], // val should be string, but is unknown
    second: [[1, 2], val => val.push(3)] // val should by number[], but is unknown
}
2 Answers

I'd suggest this:

type Item<T> = [T, (val: T) => any]


const items = {
    first: ['a', val => val.length] as Item<string>,
    second: [[1, 2], val => val.push(3)] as Item<number[]>
}

I used casting for this specific case because you have different types in the same object but I think you can rearrange your data structures to avoid casting.

See Playground Link

Here is a bit safer alternative to hinosxz's answer (Playground):

type Items = {
    [K: string]: Item<any, unknown>
}
type Item<T, U> = [T, (t: T) => U]

const item = <T, U>(...args: Item<T, U>) => args // factory fn

const items: Items = {
    first: item('a', val => val.length), // works
    second: item([1, 2], val => val.push(3)), // works

    third: item({}, val => val.length), // error (good)
    fourth: item({ 0: 1 }, val => val.push(3)) // error (good)
}

Notes:

With an index signature, all property values must have the same type. So you cannot use generic type parameters efficiently with it.

Also TS currently lacks a construct like correlated types to express, that there is a relationship between property key and its value per property instead of all properties together.

To solve the issue, we can either type all properties explicitly as usual (omitted for simplicity) or choose a wider Items base type and check all properties individually with Item type and item factory function as shown above.

You better not use a type assertion here, as its compiler checks are much more permissive than a regular assignment check. For example third and fourth wouldn't trigger an error with a type assertion.

Related