Home > Software design >  Discriminatory unions in an object, don't reflect type, when access through loops - Typescript
Discriminatory unions in an object, don't reflect type, when access through loops - Typescript

Time:09-10

TFlavour is a discriminatory union, which is then a value of an Object.

Trying to implement this, works in js, but ts gets angry. ts playground link

Expected: ts to understand discriminated unions in loops, as it understands it without loops

type TFlavour = ({ 
    natural: true,
    naturalComponent : string
}) | ({ 
    natural: false,
    artificialComponent: string
}) 

type TIceCream = Record<string, TFlavour>

const IceCreams: TIceCream = {
    CokeIceCream: {
        natural:false,
        artificialComponent: 'Coke'
    },
    Vanilla: {
        natural: true,
        naturalComponent: 'Vanilla Extract'
    },
    Mango: {
        natural: true,
        naturalComponent: 'Mango Fruit'

    }
}


const iceCreamKeys = Object.keys(IceCreams)

iceCreamKeys.forEach( item => {
    if(IceCreams[item].natural){
    console.log(IceCreams[item].naturalComponent) // ts says "Property doesn't exists.."
    }
})


if(IceCreams.Mango.natural){
    console.log(IceCreams.Mango.naturalComponent) // Works here
    }

CodePudding user response:

The problem is that the compiler doesn't know how to do narrowing on an object property like IceCreams[item] where you are indexing with a key whose type isn't known to be a specific literal type. TypeScript is only following the type of the index, not the identity. And the type of item is string. If you have item1 and item2, both of type string, then checking IceCreams[item1] wouldn't let you conclude anything about IceCreams[item2], right? And since TypeScript can't tell the difference between item1 vs item2 or item vs item, it can't narrow. This is a known limitation of TypeScript reported at microsoft/TypeScript#10530. Maybe someday it will be addressed. But for now, there's an easy workaround:

Just copy the value into a new variable, so that the problematic indexing occurs only once:

iceCreamKeys.forEach(item => {
    const x = IceCreams[item];
    if (x.natural) {
        console.log(x.naturalComponent)  // okay
    }
})

Playground link to code

CodePudding user response:

Instead of directly accessing the item with the index, try to store it in a separate variable. This way, TypeScript will recognize the right type:

iceCreamKeys.forEach( item => {
    const c = IceCreams[item]
    if(c.natural){
    console.log(c.naturalComponent) 
    }
})

(working TS Playground)

CodePudding user response:

iceCreamKeys.forEach( item => {
    if(IceCreams.item.natural){
    console.log(IceCreams.item.naturalComponent) // Accessing it like this worked
    }
})

Just found out, this works too.

  • Related