The unknown type
A parcel stamped "do not inspect" crosses the border unchecked, and the trouble shows up later. Customs has another procedure too. A box nobody knows anything about enters the country sealed. You can keep it in a warehouse and pass it on, but you may not use what is inside until someone opens the box and checks it.
In TypeScript the sealed box is the type unknown. Anything can go into it, but you take something out only after a check:
const value: unknown = JSON.parse('"hello"');
console.log(value.toUpperCase()); // ✗error TS18046: 'value' is of type 'unknown'.The message says that value has the type unknown, and while it does, tsc will not let you call a method. The box really holds the string "hello", but the compiler does not know that and does not pretend to know.
Anything goes in
Any value fits a variable of type unknown: a number, a string, null, an object. The limits start when you want to use the value:
let box: unknown = 5;
box = "five";
box = null;
const count: number = box; // ✗error TS2322: Type 'unknown' is not assignable to type 'number'.Let's walk through the example line by line:
boxis declared with the typeunknownand holds the number5.- The compiler accepts the string
"five". nullfits as well.- But
boxcannot go into anumbervariable: the compiler does not know whether it holds a number right now.
unknown forbids everything that any allows without question:
- calling methods;
- reading properties;
- putting the value into a variable of a specific type, such as
number.
A check opens the box
An ordinary JavaScript check opens the box: the typeof operator. tsc reads the condition and treats the value as a string inside the if:
const value: unknown = JSON.parse('"hello"');
if (typeof value === "string") {
console.log(value.toUpperCase()); // HELLO
}Inside the if, value has the type string, so the call to toUpperCase() passes the check. If JSON.parse had returned a number, the condition would be false, and the program would not crash on the call.
Checks for other types work the same way. After typeof value === "number" the value gets the type number inside the if, and after typeof value === "boolean" it gets boolean. A check opens the box for exactly the type you named.
In the widget one and the same value goes through three stages. Watch the tooltip: with any the method call passes without question, with unknown it is forbidden, and after the typeof check it is allowed again.
1const raw = JSON.parse('"hello"');editor hoverraw: any2const a: any = raw;3console.log(a.toUpperCase());4const u: unknown = raw;5console.log(u.toUpperCase());6if (typeof u === "string") {7 console.log(u.toUpperCase());8}
JSON.parse returns any: it cannot know what the string holds.
Here the sealed-box picture stops working. A real seal is still visible inside the country, while unknown exists only for tsc. Nothing of it is left in the JavaScript file, and the variable holds an ordinary string.
unknown or any
Interviewers often ask: "How does unknown differ from any?" The answer fits in three points:
- any value can go into either type;
anyallows everything with the value unchecked, and mistakes surface at run time;unknownallows nothing until the value is checked, andtsccatches the mistakes.
Hence the rule: when you do not know what will arrive, such as the result of JSON.parse, take unknown and check the value before using it.
Common mistake
The code checked that the value exists, but not that it is a string:
const value: unknown = JSON.parse('"hello"');
if (value) {
console.log(value.toUpperCase()); // ✗
}error TS2339: Property 'toUpperCase' does not exist on type '{}'.The condition if (value) only filters out null, undefined and other falsy values. Inside the if the compiler knows only that the value is neither null nor undefined: that is what the type {} means. It might still be a number or an object, so a string method cannot be called. You need the check typeof value === "string".
Key points
- Any value can go into
unknown, but you can use it only after a check. - Without a check,
tscreportserror TS18046: 'value' is of type 'unknown'. - After the check
typeof value === "string", the value has the typestringinside theif. anyturns the check off, whileunknownmakes you check the value, so outside data is read asunknown.