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);
}
}