JSON.parse returns any, so TypeScript accepts any type you assign the result to. That makes typing parsed JSON one line, and it also means the type is a promise you make, not something the compiler checks:
The second object has age as the string "41". TypeScript still calls it a number, because any can be assigned to anything, and the program prints 411. For JSON your own code wrote a moment ago, the annotation is fine. For data from a request, a file or local storage, validate it.
Parse into unknown
Typing the result as unknown makes the compiler insist on a check before any property is used:
The error is index.ts(4,13): error TS18046: 'data' is of type 'unknown'. Every check you then write narrows data a little further.
Validate with a Type Guard
A type guard is a function returning value is User. When it returns true, TypeScript treats the value as a User from then on, and the checks inside it are real run-time checks:
JSON.parse itself throws a SyntaxError on malformed text, so real code wraps it in try/catch as well. For large or nested payloads, hand-written guards get long; schema libraries such as Zod or Valibot let you declare the shape once and derive both the validator and the TypeScript type from it.
JSON to TypeScript Interface
Converting a JSON sample into types is mechanical. Given this response:
{
"id": 42,
"title": "Learn TypeScript",
"done": false,
"owner": { "id": 7, "name": "Ada" },
"tags": ["study", "ts"],
"dueDate": "2024-03-15T10:30:00Z",
"notes": null
}
Map each value to its type, give nested objects their own interface, and mark what can vary:
A single sample cannot tell you which fields are optional or nullable. Look at several responses, or the API's documentation, before settling on ? and | null.
Dates and the Reviver
JSON has no date type, so dates arrive as strings. The second argument of JSON.parse, the reviver, is called for every key and can rebuild them:
The reviver's value parameter is any, and so is the result, so the Order type is still trusted rather than checked. JSON.stringify(order, null, 2) indents the output by two spaces and turns the Date back into its ISO string.
JSON.stringify and What It Loses
JSON.stringify is typed to return string. The values it converts do not always come back the same, and the type does not warn you:
| Value | After JSON.stringify |
|---|---|
Date | ISO string (via its toJSON method) |
Map, Set | {} (convert with [...set] or Object.fromEntries(map) first) |
undefined, functions, symbols in an object | the key is left out |
undefined, functions, symbols in an array | null |
undefined, a function or a symbol on its own | undefined, not a string |
NaN, Infinity | null |
bigint | throws a TypeError |
The run-time rules are the same as in plain JavaScript, covered in JSON in JavaScript.
A Type for Any JSON Value
When code handles arbitrary JSON, a recursive type describes exactly what JSON can hold and rejects values it cannot:
Without the comment, the last line is a compile error because a Date object is not a JsonValue.
Importing .json Files
A .json file can be imported like a module, and TypeScript infers its type from the contents:
{ "name": "app", "port": 8080, "tags": ["a"] }
// CommonJS output, or a bundler
import config from "./config.json";
const port: number = config.port; // typed from the file: number
// An ES module under module: nodenext
import settings from "./config.json" with { type: "json" };
In TypeScript 7 this works without extra settings for module set to nodenext, node20, commonjs, esnext or preserve. Under node16 and node18 it fails with error TS2732, Cannot find module './config.json'. Consider using '--resolveJsonModule' to import module with '.json' extension., until you add "resolveJsonModule": true; setting it to false turns JSON imports off everywhere. In an ES module under nodenext or node20, the import needs the with { type: "json" } attribute (error TS1543 without it) and only the default import is allowed (error TS1544 for import { port }). tsc copies the imported .json file to outDir next to the compiled JavaScript.
Frequently Asked Questions
What type does JSON.parse return in TypeScript?
any. The compiler cannot know what a string contains, so const user: User = JSON.parse(text) compiles whatever the text holds. Assign the result to unknown and validate it when the data comes from outside your program.
How do I convert JSON to a TypeScript interface?
Take a representative sample and write one property per key: strings, numbers and booleans map to string, number and boolean, a nested object becomes its own interface, an array of objects becomes Item[], and keys that are sometimes missing get ?. Code generators such as quicktype automate this, but check their guesses against more than one sample.
How do I import a JSON file in TypeScript?
import config from "./config.json"; works in TypeScript 7 with module set to nodenext, node20, commonjs, esnext or preserve, and the result is typed from the file's contents. With node16 or node18, also set "resolveJsonModule": true. In an ES module under nodenext or node20, add the attribute Node requires: import config from "./config.json" with { type: "json" };.
Does JSON.stringify always return a string?
Its type says string, but JSON.stringify(undefined) and JSON.stringify(() => 1) return undefined at run time. Values inside objects are converted too: a Date becomes an ISO string, and Map and Set become {}.