resolveJsonModule
Allows importing .json files, typing the import from the file's actual contents.
"resolveJsonModule": boolean // default: false- Added in TypeScript 2.9
Parameters
| Parameter | Type | Description |
|---|---|---|
$resolveJsonModuleoptional | boolean= false | Enable importing files with a .json extension, generating a type for the import from the static shape of the JSON. |
Return value
booleanA typed default export for each imported .json file, with the type derived from the file's literal contents.
Errors
Without the flag, importing a .json file is error TS2732 / TS2307 — cannot find module or its corresponding type declarations.
Changelog
- PHP 2.9Introduced; originally required the node module resolution strategy.
- PHP 4.7Under module node16/nodenext, importing JSON from an ES module requires an import attribute (with { type: "json" }), matching Node's own rule.
See also
TypeScript will not resolve a .json import by default — you get "Cannot find module './settings.json' or its corresponding type declarations." resolveJsonModule makes the file part of the program and, more usefully, derives the type from what is actually in it. Import a file containing { "dry": false } and settings.dry is boolean; compare it to 2 and the compiler tells you the types have no overlap.
When to reach for it
Static configuration, feature flags, i18n message catalogues, test fixtures, and reading your own version out of package.json. In every case the payoff is that renaming a key in the JSON produces a compile error at the use site instead of an undefined at runtime.
What trips people up
The JSON file becomes a program input, so it participates in rootDir: importing ../../package.json from src commonly triggers TS6059 and reshapes your whole dist. Under module: node16 or nodenext, an ESM import of JSON also needs with { type: "json" }, because Node requires it. And the inferred type is widened — array elements are unioned into T[] rather than a tuple of literals, so a JSON array of strings gives string[], not a union of the exact values.
Example
// config/app.json
// { "name": "checkout", "retries": 3, "features": ["coupons", "gift-cards"] }
import config from "./config/app.json";
// Inferred type: { name: string; retries: number; features: string[] }
console.log(`${config.name} retries ${config.retries} times`);
console.log(config.features.includes("coupons"));
// config.retries.toUpperCase();
// -> TS2339: Property 'toUpperCase' does not exist on type 'number'.
// config.features is string[], not ["coupons", "gift-cards"], so this is allowed:
console.log(config.features.length);When to use it
- Importing a config or feature-flag file and getting a compile error when a key is renamed.
- Reading name and version out of package.json for a CLI's --version output.
- Loading a typed test fixture without hand-writing an interface for it.
- Typing an i18n message catalogue so missing keys are caught at build time.
Frequently asked questions
Why can't TypeScript find my .json module?
Because resolveJsonModule is off, which is the default. Set "resolveJsonModule": true in compilerOptions and the import resolves with a type derived from the file's contents. If you are on module node16 or nodenext and the importing file is an ES module, you also need an import attribute: import data from "./x.json" with { type: "json" }.
How do I import package.json to read the version?
Enable resolveJsonModule, then widen rootDir to the directory containing package.json and add it to include. The common failure is TS6059, because package.json sits above src and rootDir was inferred as src. Be aware this changes the output layout — dist/src/index.js instead of dist/index.js — so update your package entry points.
Does importing JSON bundle the whole file into my output?
With tsc, the JSON is copied to outDir and required at runtime. With a bundler, the contents are typically inlined into the bundle. Either way the entire file is loaded, so importing a multi-megabyte dataset this way costs both build time and runtime memory — read it from disk or fetch it instead when it is large.
More examples
Reading your own version safely
rootDir is widened to "." because package.json sits above src; leaving it at "src" gives TS6059. The import attribute is required under module nodenext when the importing file is ESM. Note the output now lands in dist/src/, which is the layout change people are surprised by.
// tsconfig.json:
// {
// "compilerOptions": {
// "resolveJsonModule": true,
// "rootDir": ".", // must include package.json, not just src/
// "outDir": "dist",
// "module": "nodenext"
// },
// "include": ["src/**/*", "package.json"]
// }
import pkg from "../package.json" with { type: "json" };
export function versionBanner(): string {
return `${pkg.name} v${pkg.version}`;
}
console.log(versionBanner());A typed message catalogue
keyof typeof turns the JSON's actual keys into a union, so every call site is checked against the real catalogue. The sorted key list prints ["farewell", "greeting"] deterministically. Adding a key to en.json immediately widens MessageKey with no code change.
// locales/en.json — { "greeting": "Hello", "farewell": "Bye" }
import en from "./locales/en.json";
type MessageKey = keyof typeof en; // "greeting" | "farewell"
function t(key: MessageKey): string {
return en[key];
}
console.log(t("greeting"));
// t("missing") -> TS2345: not assignable to 'greeting' | 'farewell'
const keys: MessageKey[] = Object.keys(en).sort() as MessageKey[];
console.log(keys);
Discussion