While rewriting an old JS file to TypeScript, I ran onto a problem with a function that is supposed to transform data from one format to another, as follows:
https://tsplay.dev/WGgYXW
type DataTypes = {
type: "typeOne"
item: number
} | {
type: "typeTwo",
item: string
} | {
type: "typeThree",
item: unknown
}
// I can't change this function, it is used in more places
const setData = (dataToSet: DataTypes) => dataToSet.item;
// The following function can process only particular types
type ReceivableDataTypes = "typeOne" | "typeTwo"
// I create a type for the tuples that are possible to receive
type IncomingData = {
[K in ReceivableDataTypes]: [K, Extract<DataTypes, {type: K}>["item"]]
}[ReceivableDataTypes]
// type IncomingData = ["typeOne", number] | ["typeTwo", string]
// as you might guess, I can't change the format of the received data
const handleIncomingData = (...incomingData: IncomingData)=>{
setData({type: incomingData[0], item: incomingData[1]})
}
handleIncomingData("typeOne", 1)
handleIncomingData("typeTwo", "test")
// this should throw an error
handleIncomingData("typeTwo", 12)
The problem is that while incomingData is typed correctly as a union of arrays, when used to create a new object, the association is broken, and both type
and item
become just two separate union types:
{
type: "typeOne" | "typeTwo";
item: string | number;
}
Breaking further inferring of the created object. I could add a manual assertion of setData({type: incomingData[0], item: incomingData[1]} as DataTypes)
which seems like a fairly sensible assumption – but I want to avoid using as
. Is there any way to retain the connection between the elements of tuple and properties of the created object, so that it would get instead inferred as the following?
{
type: "typeOne"
item: number
} | {
type: "typeTwo",
item: string
}