Why check first
In TypeScript, a dictionary is usually an object typed like { [key: string]: string }. Reading a key that isn't there does not stop the program. It just gives undefined:
const capitals: { [key: string]: string } = {"France": "Paris", "Japan": "Tokyo"};
console.log(capitals["Spain"]);
undefined
The type says every key gives a string, but types are erased when the code is compiled to JavaScript, so nothing checks that at run time. The undefined can travel a long way before something breaks. It is better to ask whether the key exists first.
The in operator
key in obj is true when key is a key of obj and false when it isn't. It looks only at the keys, so it fits an if:
console.log("Japan" in capitals, "Spain" in capitals);
console.log("Paris" in capitals, "japan" in capitals);
true false
false false
"Paris" is a value, not a key, and a key must match exactly, so "japan" is not "Japan".
Checking that a key is missing
For the opposite, wrap the whole check in parentheses and put ! in front: !("Spain" in capitals). Without the parentheses, ! applies to "Spain" alone. A non-empty string is truthy, so !"Spain" is false, and the check becomes false in capitals, which is never what you meant. The compiler reports errors for it (it says the expression is always truthy and that a boolean can't be used as a key), but the JavaScript still runs, and the condition is false every time.
Using it with else
The check also answers "which branch should run?". The variable country below holds the key to look up:
const country = "Spain";
if (country in capitals) {
console.log("The capital is " + capitals[country]);
} else {
console.log("No capital stored for " + country);
}
No capital stored for Spain
Only the branch where the key exists reads the value, so the code that uses the value can rely on it being there. The same shape works for any lookup whose key comes from somewhere else, such as a name typed by a user.
The exercise, step by step
In pets, each key is a pet's name and each value is the pet's kind: Rex is a dog, Tom a cat, Polly a parrot.
- Is "Tom" a key? Write an
ifwith the condition"Tom" in pets. Inside it,pets["Tom"]is neverundefined, so log"Tom is a " + pets["Tom"]. - Is "cat" not a key? Write an
ifwith!("cat" in pets). "cat" is inpetsonly as a value, Tom's kind, andinlooks only at keys, so the condition is true. Logcat is not a key.
const pets: { [key: string]: string } = {"Rex": "dog", "Tom": "cat", "Polly": "parrot"};
if ("Tom" in pets) {
console.log("Tom is a " + pets["Tom"]);
}
if (!("cat" in pets)) {
console.log("cat is not a key");
}
Tom is a cat
cat is not a key
Traps
- Don't test with the value.
if (capitals["Spain"])treats a key whose value is""or 0 as missing. With{"a": ""}, that check says the key is missing, while"a" in objcorrectly saystrue. - Inherited names. Every object also inherits some names, so
"toString" in capitalsistrueeven though you never added it. Ordinary names like those in the lesson are not affected. If it could matter,Object.hasOwn(capitals, "toString")checks only the object's own keys and givesfalse. - Missing parentheses with
!. See above: write!("x" in obj).
Also remember that keys are strings. A number used as a key is turned into text, so 1 in obj and "1" in obj ask the same question. When you build a check from a variable, make sure the variable holds the exact key you stored.
Key takeaways
key in objis abooleanthat looks only at keys.- Check before reading, because a missing key reads as
undefined. - Put the whole
incheck in parentheses before applying!.
