This is a shortcoming of Typescript and a design limitation. It correctly types item.value
as boolean | string[]
and conversely it correctly types item.render
as typeof arrayToString | typeof booleanToString
. However, in order to ensure type-safety, Typescript doesn't know which function it is, so to guarantee it will not error on either, it intersects the parameters, hence boolean & string[]
. The intersection of these two primitives is never
. This is because Typescript types doesn't have iterable discrimination.
A possible workaround is to cast it as never
data.map(({value, render}) =>{
render(value as never);
})
My recommendation (if you are setting data this way) is to create a type to ensure that render always accepts the associated value as parameters
type ObjectWithRender<T> = {
value: T
render: (arg: T) => string
}
const data: [
ObjectWithRender<boolean>,
ObjectWithRender<[string, string]>
] = [
{
value: true,
render: booleanToString
},
{
value: ["a","b"],
render: arrayToString
}
]
Or alternatively create a helper function to infer value and renderValue... But this carries some downsides, namely your functions will now have to accept readonly args. It'll also affect compilation-to-runtime with an effectively useless function.
function validateData<T extends ReadonlyArray<any>>(data: {
[K in keyof T]: {
value: T[K]
render: (arg: T[K]) => string
}
}
) {return data}
// Will throw an error if value isn't the render arg
const validatedData = validateData(
[
{
value: true,
render: booleanToString
},
{
value: ['a', 'b'],
render: arrayToString
}
] as const
)
View on TS Playground