I've a function that tries to find a value in an array of numbers. The value can be either an object or a number. If the value is an object, there's a 'key' property that is used to get the number value from the object.
I'm trying to use Function Overloading to have a single function that can handle both cases.
type ObjectType = {
[key: string]: number
}
type FunctionType = {
<T extends ObjectType>(v: T, list: number[], key: string): T | undefined
(v: number, list: number[]): number | undefined
}
const find: FunctionType = <T extends ObjectType | number,>(v: T, list: number[], key?: string)=>{
const value = typeof v === 'number' ? v : v[key]
return list.find((item)=>{
return item === value
})
}
This creates an error Type undefined cannot be used as an index type
But in this branch, the value of v is an object, so how is key not defined?
TS-Playground
Thank you
A solution was proposed by Alex Wayne, but it doesn't properly narrow value to a number.
If instead of calling list.find, we called a custom function that only accepts numbers, typescript would throw an error
const findNumber = (v: number, list: number[])=>{
/* Do stuff with v as a number */
return list.indexOf(v)
}
const find: FunctionType = <T extends ObjectType | number>(v: T, list: number[], key?: keyof T)=>{
const value = (key && typeof v === 'object') ? v[key] : v
return list[findNumber(value, list)]
}
Type 'ObjectType' is not assignable to type 'number'
TS-Playground
The implementation function has no idea what its overloads are. This means that an overload function implementation must still be completely valid if the overload function signatures are removed.
So while it is true that if typeof v === 'number' then typeof key === 'string' the compiler doesn't know that.
If you look purely at this function signature:
<T extends ObjectType | number,>(v: T, list: number[], key?: string)=>{
Then you see that find({ someKey: 1 }, [1,2,3]) is a valid invocation. The overloads prevent that invocation, but again this function must be valid without those overloads.
Which means to fix this you'll have to test for the presence of key before you use it.
const value = (key && typeof v === 'number') ? v : v[key]
However, this gives a new problem:
Type 'undefined' cannot be used as an index type.(2538)
I believe this is because typeof v === 'number' is not a sufficient refinement. I'm not sure exactly what else it thinks it could be, but if you invert the ternary and instead refine by typeof v === 'object' typescript is happy.
const value = (key && typeof v === 'object') ? v[key] : v
Playground
Lastly, you can probably improve type safety here by using key: keyof T instead.
const find: FunctionType = <T extends ObjectType | number>(v: T, list: number[], key?: keyof T)=>{
const value = (key && typeof v === 'object') ? v[key] : v
return list.find((item)=>{
return item === value
})
}
See playground
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With