Typescript: Replace any by unknown in definition files

Created on 3 Aug 2018  ·  13Comments  ·  Source: microsoft/TypeScript

Since the new unknown type was added, it makes sense to replace a lot (if not all) usage of any in the definition files with unknown. For example:

Array.isArray(a: unknown): a is unknown[];
JSON.parse(a: string): unknown;

However, this change will cause many errors in projects currently using the fact that the returned types are not type-checked. This means that the change could be controversial.

Awaiting More Feedback Suggestion

Most helpful comment

This is one of the biggest blind spots regarding type safety in big codebases. You can warn against using explicit any with linting, but you can't change the types of all the declaration files

All 13 comments

This is effectively the same proposal as strictAny; see #24737

If it is true that any has no use over unknown except for compiler error suppression, I might make a script that replaces all usages for my own definition files.

This is effectively the same proposal as strictAny; see #24737

This is not exactly correct, the strictAny flag proposal is a project-level compiler flag, whereas this is a proposal to make a change to the definition files to make them more exact.

It makes more sense for JSON.parse() to return an unknown in all cases rather than any in all cases. #24737 makes a case that users need to know unknown better, and I agree, this is a step in that direction.

Also, @ThomasdenH, I think you meant

Array.isArray(a: unknown): a is unknown[];

Yes, you're right!

I think the main hurdle here is potentially causing confusion when code breaks, but all code should be easy to fix. In the worst case scenario 'as any' will do.

For now you can override the types yourself and enjoy more strict type checks:

declare global {
  interface JSON {
    parse(text: string, reviver?: (key: any, value: any) => any): unknown;
  }

  interface ArrayConstructor {
    isArray(a: unknown): a is unknown[];
  }

  interface Body {
    json(): Promise<unknown>;
  }
}

See also: #27265

Also, Array.isArray(a: unknown) a is unknown[] wouldn’t break the following code or any other code which is using it on a union of types to determine if the argument is an array or not:
```ts
function foo(a: string | string[]) {
if (!Array.isArray(a)) {
a = [a];
}
return a.filter(s => !!s);
}

@mohsen1 Actually, that should be:
```ts
declare global {
interface JSON {
/* JSON doesn't support symbol keys, and number keys
* are coerced to strings, even in arrays */

    /**
     * Converts a JavaScript Object Notation (JSON) string into an object.
     * @param text A valid JSON string.
     * @param reviver A function that transforms the results. This function is called for each member of the object.
     * If a member contains nested objects, the nested objects are transformed before the parent object is.
     */
    parse(text: string, reviver?: (this: unknown, key: string, value: unknown) => unknown): unknown;

    /**
     * Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
     * @param value A JavaScript value, usually an object or array, to be converted.
     * @param replacer A function that transforms the results.
     * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
     */
    stringify(text: string, replacer?: (this: unknown, key: string, value: unknown) => unknown, space?: string | number): unknown;
}

interface ArrayConstructor {
    isArray(a: unknown): a is unknown[];
}

interface Body {
    json(): Promise<unknown>;
}

}

This is one of the biggest blind spots regarding type safety in big codebases. You can warn against using explicit any with linting, but you can't change the types of all the declaration files

This change would be greatly appreciated: I'm trying to check data submitted by users which is supposed to be {foo: string}[] and initially typed as unknown.

As part of the checking process I'm calling Array.isArray, which inconveniently types the data to any[], allowing code such as data[0].bar.baz.qux to be written inside my type guard, which would throw a runtime error.

Currently a dirty workaround I'm using is creating a new var with the appropriate type after the isArray check:

const typedData = data as unknown[];

@RyanCavanaugh now that you've backed off from strictAny, would you reconsider this? The original use case here (which motivated strictAny in the first place) would still be pretty valuable.

Just to note that Body.json() during fetch should also return type Promise<unknown> instead of Promise<any>

Was this page helpful?
0 / 5 - 0 ratings