Search code examples
javascripttypescriptdiscriminated-union

Strange union type behavior in TypeScript


Could anyone explain the following behavior in TypeScript (2.4.1)?

Scenario: I have a button which could be "red" or "red and round" (modifications). I would like to have the following syntax to describe it:

button.mods = "red";
button.mods = ["red", "round"];
button.mods = { red: true, round: false };

To describe all of this, I use the following interfaces:

interface HasMods<T extends string>{ 
    mods: T | T[] | { [key in T]?: boolean } 
}

interface Button extends HasMods<"round" | "red"> { 
}

Ok, now we can do some testing:

let b: Button;
b.mods = "red"; //ok, correct 
b.mods = "green"; //error, correct

b.mods = ["red"]; //ok, correct
b.mods = ["green"]; //error, correct

b.mods = {red: true}; //ok, correct
b.mods = {red: true, green: true}; //error, correct

So far everything is perfect. But now a mystery:

b.mods = {red: true, map: false}; //ok, why ???

Why is the value "map" valid for my object of type { [key in T]?: boolean } where T is "red" | "round"? "map" is neither "red" or "round".

Actually, all array methods are valid here - "every", "copyWithin", etc...


Solution

  • If you take a look in the proto definition of the array, "map" is one of the attributes.

    So when you're comparing "map in T" it's true, because it's being compared inside an array structure, if it were an object it will be false

    console.log("map" in {"element":4}) // false
    console.log("map" in [4]) //true