TypeScript: Replace any by unknown in definition files

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.

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 74
  • Comments: 24 (9 by maintainers)

Most upvoted comments

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

@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.

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.

Typescript 5 would be a great time to introduce this.

I’m a bit confused. I don’t see any case against unknown being the correct return value in the comments above and this is a pretty obvious source of problems. Given that json is often input of some kind (network, user, files) this issue may even be a major source of security problems (third parties remotely injecting incorrectly typed data via request.json() seems particularly problematic). Given that this issue has been open for years at this point, could someone give an indication what the problem with the suggested solution is?

@RyanCavanaugh I think this is a good proposal, and different from previous ones. One of the places where type safety is most important is when interacting with the outside world - when parsing JSON, making fetch requests, etc. Currently these are all typed as any, effectively allowing us to declare the returned type as anything we want.

To be really type-safe, the return value in these cases should be unknown, and it should be the programmer’s responsibility to validate the returned type (e.g. via a custom type guard).

For example, this code is currently valid:

interface CatInfo {
  age: number;
  breed: string;
}

fetch("some-url").then(res => res.json()).then((cat: CatInfo) => {
   // do something assuming the response is a cat
})

But in reality this could easily break. If the type returned by res.json() were Promise<unknown> (rather than Promise<any>), this code would no longer be valid, and we’d need validate the type of the response:

function isCatInfo(value: unknown): value is CatInfo {
   // ...
}

fetch("some-url").then(res => res.json()).then((response) => {
  if (isCatInfo(response)) {
    // the type of response is now CatInfo
  } 
})

I’d love to see a way to at least opt-in to this kind of behavior, so we won’t need to override the global type definitions (as in the example by @ExE-Boss) .

Would be great if this was considered for the next major version update 💞

@RyanCavanaugh Is there any update on this? It’s been 5 years since this was labeled Awaiting more feedback. No arguments against it seem to have been provided, not even about “breaking change” and “not being able to opt out via a compiler option”.

This seems like a major and unnecessary source of unsoundness. Especially changing JSON.parse would force developers to validate data at the edge of the application, or explicitly opt-out of type safety by asserting as any, instead of defaulting to unsound code.

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

I wanted to express support for @lizraeli’s response, since it’s been a problem that we will eventually need to resolve in the code. As I was reading up to be able open this issue myself and saw this is already opened (several times #26993, #48697 but with different proposals).

If you’re already seasoned as a TS developer, you’ll probably know that as X is usually a bad idea in runtime specially because it can introduce erratic behaviour like something coming not the way you expect from outside. So, usually, the default course of action is that we do something with type guards:

type TheType = { required: 'prop' }
function isTypeCorrect (v: unknown): v is TheType { ... }

const value = await (await fetch('something')).json()
if (isTypeCorrect(value)) {
 // do things
}

But, and I’ve seen this happen some times already (especially with someone that’s starting in TS), is that the types are used like:

type TheType = { required: 'prop' }
const value: TheType = await (await fetch('something')).json()

And this is perfectly valid, it can also be true 99% of the times as well, but there’s no indication or correct safeguard asserting that it is, which – at least for me – is a bit unsettling, especially if you’re working in critical systems.

My initial suggestion before the readup would be to make parse generic with parse<T>, but after #48697 it seems correct to assume that it’s even better to just return unknown and make sure that the type guard or the check will be required.

But you can always do as TheType

That’s also true, but this would be true for every case, and it’s easier to pinpoint in a review when this is done, it’s also easier to write an TSLint rule for this as well, but it’s not that easy to figure out if the result from fetch is the expected type or if the result is being checked properly.

So I wanted to express the full support for this, especially saying that this is something widely used (we can see numbers in https://github.com/total-typescript/ts-reset where the users alone are 4.1k in GH and 172k downloads/month in npm)

Fans of type-safety here at Beslogic would like this! I’ve been changing lots of types any for unknown.

I’d like to throw in my support for this proposal. Looks like it hasn’t seen much attention lately.

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[];

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.