I have been working on a helper function called get_item
which is supposed to return a single item from an object's property. However, I am facing difficulties in getting the correct typings for it.
// without typings
function get_item(foo, key, index) {
const array = foo[key];
// check if the index exists
if (index in array === false) throw new Error("Item " + index + " not found for " + key);
return array[index];
}
// usage example
const item = get_item({bar:[0,1,2]}, 'bar', 0);
Despite my efforts, I seem to struggle when trying to infer the ArrayItemType
of array
.
Although returning the type of array
based on the provided key
is not an issue, as demonstrated here.
However, I specifically want to work with properties of type Array
in my foo
object and retrieve the type of a single item in the selected array using the key
. Unfortunately, TypeScript seems to lose track of the relation to the key
property when accessing the array with the index operator.
Typescript 4.5.4 Playground (same code as below)
/**
* I want to get the item at position 'index'
* from the properties with name 'key'
* of the structure 'Foo'
*/
function get_item<
KEY extends keyof Foo, // "zoo" | "bar"
>(foo: Foo, key: KEY, index:number)
: ArrayItemType<Foo[KEY]> // determines the type of a single element for the property 'key' beeing number|string
{
const array: Foo[KEY] = foo[key]; // this can be Array<number> or Array<string> depending on 'key'
// check if the index exists
if (index in array === false) throw new Error("Item " + index + " not found for " + key);
const item = array[index]; // at this point the type seems to have collapsed to number|string,
// discarding the fact that we know the exact type of 'array' for a given 'key'
return item; // Error
// Type 'string | number' is not assignable to type 'ArrayItemType<Foo[KEY]>'.
// Type 'string' is not assignable to type 'ArrayItemType<Foo[KEY]>'.
}
type ArrayItemType<ARRAY> = ARRAY extends Array<infer ITEM> ? ITEM : never;
/**
* Some interface with a few properties of type Array<any>
*/
interface Foo {
bar : Array<number>;
zoo : Array<string>;
}
const foo : Foo = {
bar: [0,1],
zoo: ["a", "b"],
};
// determines correct types here, this is the way i want it
const number_item :number = get_item(foo, 'bar', 1);
const string_item :string = get_item(foo, 'zoo', 1);
Although adding as ArrayItemType<Foo[KEY]>
resolves the issue, I prefer to minimize such type castings especially when the reason behind their necessity eludes me (leading me to believe I misunderstood something in my code).
So, could there be something I am overlooking?
Is this scenario potentially undoable?
Or might the problem lie within KEY extends keyof Foo
, allowing unexpected values?