I have an object that can have multiple types that can be discriminated through the “type” property. I want to write a function that would take a type as a parameter and return correctly typed item or undefined, depending if the initial object is currently the correct type, as follows:
type HorribleObject = {
type: "foo",
item: {
foo: string
}
} | {
type: "bar",
item: {
bar: number
}
} | {
type: "",
item: Record<string, never>
}
let objectIGet: HorribleObject = {type:"", item:{}}
// this works...
const getBarItem = 'bar' in objectIGet.item ? objectIGet.item.bar : undefined;
// ...but this doesn't
type GettableItemType = "foo" | "bar"
const getItemByType = (type: GettableItemType) => type in objectIGet.item ? objectIGet.item[type] : undefined;
Playground link: https://tsplay.dev/wEejkw
When I hand-type every possible getter, everything is fine because then the discriminator in the 'bar' in objectIGet.item
has a singular type "bar"
and the compiler correctly assumes that the discriminated object I get is the one that has a property item.bar
. But hand-typing a series of getters makes for a rather ugly code in a real-life scenario when there are many more possible types. While trying to write a generic function I unfortunately run into the following problem: because type
is a union of strings, the discriminator type in objectIGet
produces a union of object types. While we can quite safely assume that the object we get is the one that has the property passed in the params, the compiler simply doesn’t know this.
Now, I can use an assertion in the function as follows:
const getItemByType = <T extends GettableItemType>(type: T) =>
type in objectIGet.item
? (objectIGet.item as Record<T, Extract<HorribleObject, {item: {[K in T]: any}}>["item"][T]>)[type]
: undefined;
( https://tsplay.dev/NdXJ0N )
This produces correctly typed results and seems to be a reasonable assumption, but somehow doesn’t seem right. Is there some more correct way of doing this?