any and unknown in TypeScriptIn TypeScript, any and unknown are types that contain all values. In this blog post, we examine how they work.
any and unknown are so-called top types in TypeScript. Quoting Wikipedia:
The top type [...] is the universal type, sometimes called the universal supertype as all other types in any given type system are subtypes [...]. In most cases it is the type which contains every possible [value] in the type system of interest.
That is, when viewing types as sets of values (for more information on what types are, see “What is a type in TypeScript? Two perspectives”), any and unknown are sets that contain all values. As an aside, TypeScript also has the bottom type never, which is the empty set.
any If a value has type any, we can do everything with it:
function func(value: any) {
// Only allowed for numbers, but they are a subtype of `any`
5 * value;
// Normally the type signature of `value` must contain .propName
value.propName;
// Normally only allowed for Arrays and types with index signatures
value[123];
}
Every type is assignable to type any:
let storageLocation: any;
storageLocation = null;
storageLocation = true;
storageLocation = {};
Type any is assignable to every type:
function func(value: any) {
const a: null = value;
const b: boolean = value;
const c: object = value;
}
With any we lose any protection that is normally given to us by TypeScript’s static type system. Therefore, it should only be used as a last resort, if we can’t use more specific types or unknown.
JSON.parse() The result of JSON.parse() depends on dynamic input, which is why the return type is any (I have omitted the parameter reviver from the signature):
JSON.parse(text: string): any;
JSON.parse() was added to TypeScript before the type unknown existed. Otherwise, its return type would probably be unknown.
String() The function String(), which converts arbitrary values to strings, has the following type signature:
interface StringConstructor {
(value?: any): string; // call signature
// ···
}
unknown The type unknown is a type-safe version of the type any. Whenever you are thinking of using any, try using unknown first.
Where any allows us to do anything, unknown is much more restrictive.
Before we can perform any operation on values of type unknown, we must first narrow their types via:
function func(value: unknown) {
// @ts-ignore: Object is of type 'unknown'.
value.toFixed(2);
// Type assertion:
(value as number).toFixed(2); // OK
}
Equality:
function func(value: unknown) {
// @ts-ignore: Object is of type 'unknown'.
value * 5;
if (value === 123) { // equality
// %inferred-type: 123
value;
value * 5; // OK
}
}
function func(value: unknown) {
// @ts-ignore: Object is of type 'unknown'.
value.length;
if (typeof value === 'string') { // type guard
// %inferred-type: string
value;
value.length; // OK
}
}
function func(value: unknown) {
// @ts-ignore: Object is of type 'unknown'.
value.test('abc');
assertionFunction(value);
// %inferred-type: RegExp
value;
value.test('abc'); // OK
}
function assertionFunction(arg: unknown): asserts arg is RegExp {
if (! (arg instanceof RegExp)) {
throw new TypeError('Not a RegExp: ' + arg);
}
}