Skip to content

JSON.parse is not supposed to return any #26993

Closed

Description

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

Activity

  1. Bnaya commented on Sep 10, 2018

    @Bnaya

    Maybe unknown is more correct

  2. nmain commented on Sep 10, 2018

    @nmain

    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)

  3. RyanCavanaugh commented on Sep 10, 2018

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

  4. kf6kjg commented on Feb 23, 2023

    @kf6kjg

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

  5. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions