Skip to content

JSON.parse is not supposed to return any #26993

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
alepee opened this issue Sep 10, 2018 · 4 comments
Closed

JSON.parse is not supposed to return any #26993

alepee opened this issue Sep 10, 2018 · 4 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@alepee
Copy link

alepee commented Sep 10, 2018

TypeScript Version: 3.1.0-dev.20180828
Search Terms: JSON parse type any
Code

JSON.parse // is expected to return "any"

Expected behavior:
JSON parse spec expect it to return object | number | string | boolean | null

Actual behavior:
JSON parse implementation at lib/lib.es5.d.ts expect it to return any

@Bnaya
Copy link

Bnaya commented Sep 10, 2018

Maybe unknown is more correct

@nmain
Copy link

nmain commented Sep 10, 2018

Assuming no receiver parameter was passed, you could express it more exactly using something like this (which has been proposed before, I think):

type JSONValue = string | number | boolean | null | JSONObject | JSONArray;
interface JSONObject { [key: string]: JSONValue; }
interface JSONArray extends Array<JSONValue> { }

But how useful would that be? I think most code wouldn't want a structure like this, unless you wrote a library that was specifically for making modifications on the structure of any JSON. In most realistic use cases, you're going to want to cast the return of JSON.parse to a type specific for your application, probably using runtime checks and type guards along the way. In those cases you want any or maybe unknown (see #26188)

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Sep 10, 2018
@RyanCavanaugh
Copy link
Member

But how useful would that be? I think most code wouldn't want a structure like this, unless you wrote a library that was specifically for making modifications on the structure of any JSON. In most realistic use cases, you're going to want to cast the return of JSON.parse to a type specific for your application, probably using runtime checks and type guards along the way. In those cases you want any or maybe unknown

This is exactly correct

@kf6kjg
Copy link

kf6kjg commented Feb 23, 2023

As mentioned in #26188 and several other places, returning any from JSON.parse is not correct. According to the spec and the practice JSON.parse returns a more specialized range of types than any implies - it's even more specialized than unknown implies when there is no reviver function.

To be clear: JSON.parse(string) cannot and will not ever return a Buffer, Error, or any such data structures. Thus we can make SOME assumptions about its output. That said, adding a reviver function breaks the rule and I don't see a way to infer the resulting data structure.

To handle all this we could have two signatures for JSON.parse:

  1. JSON.parse(text: string): JsonType - Parses a stringified JSON value into a JSON data structure. The user may want to specialize the output, and they can via casting and checks, but the default is now at least clear and correct.
  2. JSON.parse(text: string, reviver: (key: string, value: JsonType) => unknown): unknown - Parses a stringified JSON value, but makes no assumptions about the structure of the output since the reviver could mangle the output types in many different ways.

JsonType is defined based on the JSON spec, and there are several well-stated type unions that express the concept.

Some may be frustrated by the compiler telling them that they really don't know the type returned. That's understandable: all of us had to unlearn bad habits when we went from JavaScript to TypeScript.

All that said, I'd REALLY appreciate JSON.parse returning unknown rather than any if the more complex JSON types are too mush - after all that's WHY I choose TypeScript over raw JavaScript: the former gives me a layer of type safety that yells at me in the compiler if I do something stupid. As it stands JSON.parse('[]').notexist is a runtime error instead of a compiler error.

Yes this would be a breaking change. But a welcome one. It could also be behind a flag like the change to catch so that it could be introduced in a minor version.

As an aside, the project ts-reset provides a quick hack workaround/prototype of some of this plus a few other cases. However its opt-in mechanism means that it can't be used in libraries. 😢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants