Importing JSON file in TypeScript
Asked Answered
L

16

475

I have a JSON file that looks like following:

{

  "primaryBright":    "#2DC6FB",
  "primaryMain":      "#05B4F0",
  "primaryDarker":    "#04A1D7",
  "primaryDarkest":   "#048FBE",

  "secondaryBright":  "#4CD2C0",
  "secondaryMain":    "#00BFA5",
  "secondaryDarker":  "#009884",
  "secondaryDarkest": "#007F6E",

  "tertiaryMain":     "#FA555A",
  "tertiaryDarker":   "#F93C42",
  "tertiaryDarkest":  "#F9232A",

  "darkGrey":         "#333333",
  "lightGrey":        "#777777"
}

I'm trying to import it into a .tsx file. For this I added this to the type definition:

declare module "*.json" {
  const value: any;
  export default value;
}

And I'm importing it like this.

import colors = require('../colors.json')

And in the file, I use the color primaryMain as colors.primaryMain. However I get an error:

Property 'primaryMain' does not exist on type 'typeof "*.json"

Literalminded answered 24/4, 2018 at 8:5 Comment(3)
Your module declaration and your import form disagree.Underestimate
Do you mind showing an example? I'm typescript noob.Literalminded
Possible duplicate of Typescript compiler error when importing json fileMon
U
217

The import form and the module declaration need to agree about the shape of the module, about what it exports.

When you write (a suboptimal practice for importing JSON since TypeScript 2.9 when targeting compatible module formatssee note)

declare module "*.json" {
  const value: any;
  export default value;
}

You are stating that all modules that have a specifier ending in .json have a single export named default.

There are several ways you can correctly consume such a module including

import a from "a.json";
a.primaryMain

and

import * as a from "a.json";
a.default.primaryMain

and

import {default as a} from "a.json";
a.primaryMain

and

import a = require("a.json");
a.default.primaryMain

The first form is the best and the syntactic sugar it leverages is the very reason JavaScript has default exports.

However I mentioned the other forms to give you a hint about what's going wrong. Pay special attention to the last one. require gives you an object representing the module itself and not its exported bindings.

So why the error? Because you wrote

import a = require("a.json");
a.primaryMain

And yet there is no export named primaryMain declared by your "*.json".

All of this assumes that your module loader is providing the JSON as the default export as suggested by your original declaration.

Note: Since TypeScript 2.9, you can use the --resolveJsonModule compiler flag to have TypeScript analyze imported .json files and provide correct information regarding their shape obviating the need for a wildcard module declaration and validating the presence of the file. This is not supported for certain target module formats.

Underestimate answered 24/4, 2018 at 8:43 Comment(6)
@Royi that depends on your loader. For remote files consider using await import('remotepath');Underestimate
@jbmusso I added some information regarding the improvements introduced by later versions of TypeScript but I don't think this answer is out of date because it is conceptual. However, I'm open to suggestions for further improvements.Underestimate
The risk is that some people could simply copy/paste the first lines of your answer, only fixing the symptom and not the root cause. I believe @kentor's answer yields greater details and provides a more complete answer. A recommendation would be to move your Note on top of your answer, clearly stating that this is the correct way to tackle this issue as of today.Psychopath
import {default as yourPreferredName} from "any.json"; works like a charmBristow
@Bristow import {default as yourPreferredName} from "any.json"; is proper but it's usually preferable to write import yourPreferredName from "any.json"; which has precisely the same meaning.Underestimate
@kentor's solution is great if you want type checking on your json files, but that could come with a TS compilation/type-checking performance cost. Using declare module "*.json" listed here is great if you want to import json files without type checking them (boosting TS analysis performance).Housework
R
833

With TypeScript 2.9.+ you can simply import JSON files with benefits like typesafety and intellisense by doing this:

import colorsJson from '../colors.json'; // This import style requires "esModuleInterop", see "side notes"
console.log(colorsJson.primaryBright);

Make sure to add these settings in the compilerOptions section of your tsconfig.json (documentation):

"resolveJsonModule": true,
"esModuleInterop": true,

Side notes:

  • Typescript 2.9.0 has a bug with this JSON feature, it was fixed with 2.9.2
  • The esModuleInterop is only necessary for the default import of the colorsJson. If you leave it set to false then you have to import it with import * as colorsJson from '../colors.json'
Robb answered 4/6, 2018 at 6:14 Comment(9)
You don't necessarily need esModuleInterop, but then you have to do import * as foo from './foo.json'; -- the esModuleInterop was causing other problems for me when I tried enabling it.Huguenot
You are right, I should have added that as a side note :-).Robb
Note: Option "resolveJsonModule" cannot be specified without "node" module resolution strategy, so you also need to put "moduleResolution": "node" into your tsconfig.json. It also comes with the downside, that the *.json files you want to import need to be inside of "rootDir". Source: blogs.msdn.microsoft.com/typescript/2018/05/31/…Astera
@Huguenot that's correct but import * as foo from './foo.json' is the wrong import form. It should be import foo = require('./foo.json'); when not using esModuleInteropUnderestimate
Only part i needed was "resolveJsonModule": true and all is wellAfterdinner
@BennyNeugebauer - You wrote: "It also comes with the downside, that the *.json files you want to import need to be inside of "rootDir"" --- If that was in your link when you wrote the comment, it is gone now. Instead the comment shows an example where the json file is not in the root directory.Fortissimo
I also needed "allowSyntheticDefaultImports": true for TS 2.9.2Babyblueeyes
import * as foo from './bar.json' worked for me. TS 3.8Trapeze
Using "resolveJsonModule": true is a correct setting. Somehow with my case, I use Visual Studio Code and it still show the error. Luckily, I just closed and reopened the Visual Studio Code, the issue resolved. So I suggest we should restart the IDE after changing tsconfig.json.Williamswilliamsburg
U
217

The import form and the module declaration need to agree about the shape of the module, about what it exports.

When you write (a suboptimal practice for importing JSON since TypeScript 2.9 when targeting compatible module formatssee note)

declare module "*.json" {
  const value: any;
  export default value;
}

You are stating that all modules that have a specifier ending in .json have a single export named default.

There are several ways you can correctly consume such a module including

import a from "a.json";
a.primaryMain

and

import * as a from "a.json";
a.default.primaryMain

and

import {default as a} from "a.json";
a.primaryMain

and

import a = require("a.json");
a.default.primaryMain

The first form is the best and the syntactic sugar it leverages is the very reason JavaScript has default exports.

However I mentioned the other forms to give you a hint about what's going wrong. Pay special attention to the last one. require gives you an object representing the module itself and not its exported bindings.

So why the error? Because you wrote

import a = require("a.json");
a.primaryMain

And yet there is no export named primaryMain declared by your "*.json".

All of this assumes that your module loader is providing the JSON as the default export as suggested by your original declaration.

Note: Since TypeScript 2.9, you can use the --resolveJsonModule compiler flag to have TypeScript analyze imported .json files and provide correct information regarding their shape obviating the need for a wildcard module declaration and validating the presence of the file. This is not supported for certain target module formats.

Underestimate answered 24/4, 2018 at 8:43 Comment(6)
@Royi that depends on your loader. For remote files consider using await import('remotepath');Underestimate
@jbmusso I added some information regarding the improvements introduced by later versions of TypeScript but I don't think this answer is out of date because it is conceptual. However, I'm open to suggestions for further improvements.Underestimate
The risk is that some people could simply copy/paste the first lines of your answer, only fixing the symptom and not the root cause. I believe @kentor's answer yields greater details and provides a more complete answer. A recommendation would be to move your Note on top of your answer, clearly stating that this is the correct way to tackle this issue as of today.Psychopath
import {default as yourPreferredName} from "any.json"; works like a charmBristow
@Bristow import {default as yourPreferredName} from "any.json"; is proper but it's usually preferable to write import yourPreferredName from "any.json"; which has precisely the same meaning.Underestimate
@kentor's solution is great if you want type checking on your json files, but that could come with a TS compilation/type-checking performance cost. Using declare module "*.json" listed here is great if you want to import json files without type checking them (boosting TS analysis performance).Housework
G
34

Here's how to import a json file at runtime

import fs from 'fs'
var dataArray = JSON.parse(fs.readFileSync('data.json', 'utf-8'))

This way you avoid issues with tsc slowing down or running out of memory when importing large files, which can happen when using resolveJsonModule.

Georgiana answered 4/5, 2021 at 8:26 Comment(2)
Yea but if its such a large file that it slows the compiler out of memory, you just called FS.readFileSync on that huge file, then you parsed it, synchronously. Don't you think it would be better to load it asynchronously? Given each situation is different, but as a generic answer to an issue I just don't see much benefit here.Benumb
Good point, but for some reason I had tsc crashing even for relatively small json files (< 1Mb, 20k lines), and reading&parsing the same file synchronously at runtime was not an issue. Not sure why that would be the case, I think tsc just doesn't handle compiling large arrays very well.Georgiana
S
34

In my case I needed to change tsconfig.node.json:

{
  "compilerOptions": {
    ...
    "resolveJsonModule": true
  },
  "include": [..., "colors.json"]
}

And to import like that:

import * as colors from './colors.json'

Or like that:

import colors from './colors.json'

with "esModuleInterop": true

Stereoisomer answered 22/3, 2022 at 13:5 Comment(0)
U
18

It's easy to use typescript version 2.9+. So you can easily import JSON files as @kentor decribed.

But if you need to use older versions:

You can access JSON files in more TypeScript way. First, make sure your new typings.d.ts location is the same as with the include property in your tsconfig.json file.

If you don't have an include property in your tsconfig.json file. Then your folder structure should be like that:

- app.ts
+ node_modules/
- package.json
- tsconfig.json
- typings.d.ts

But if you have an include property in your tsconfig.json:

{
    "compilerOptions": {
    },
    "exclude"        : [
        "node_modules",
        "**/*spec.ts"
    ], "include"        : [
        "src/**/*"
    ]
}

Then your typings.d.ts should be in the src directory as described in include property

+ node_modules/
- package.json
- tsconfig.json
- src/
    - app.ts
    - typings.d.ts

As In many of the response, You can define a global declaration for all your JSON files.

declare module '*.json' {
    const value: any;
    export default value;
}

but I prefer a more typed version of this. For instance, let's say you have configuration file config.json like that:

{
    "address": "127.0.0.1",
    "port"   : 8080
}

Then we can declare a specific type for it:

declare module 'config.json' {
    export const address: string;
    export const port: number;
}

It's easy to import in your typescript files:

import * as Config from 'config.json';

export class SomeClass {
    public someMethod: void {
        console.log(Config.address);
        console.log(Config.port);
    }
}

But in compilation phase, you should copy JSON files to your dist folder manually. I just add a script property to my package.json configuration:

{
    "name"   : "some project",
    "scripts": {
        "build": "rm -rf dist && tsc && cp src/config.json dist/"
    }
}
Ungraceful answered 24/7, 2018 at 8:44 Comment(3)
Is rm -rf a Linux/Unix thing, or will that work on ol' Windurz also?Jiggle
thank you, my typings.d.ts was out of place. As soon as I moved to /src the error message disappeared.Spike
@Jiggle It's indeed only a Linux/Unix thing.Trench
I
12

In an Angular (typescript) app, I needed to include a .json file in my environment.ts. To do so, I had to set two options in tsconfig:

{
  "compilerOptions": {
    "moduleResolution": "node",
    "resolveJsonModule": true
  }
}

Then, I could import my json file into the environment.ts:

import { default as someObjectName } from "../some-json-file.json";
Isborne answered 8/2, 2022 at 21:28 Comment(0)
A
11

You should add

"resolveJsonModule": true

as part of compilerOptions to tsconfig.json.

Apuleius answered 28/4, 2021 at 23:18 Comment(1)
This doesn't explain why the code in the question is incorrect. The only thing you suggest is part of the several existing answers yet stripped of context.Underestimate
W
9

Often in Node.js applications a .json is needed. With TypeScript 2.9, --resolveJsonModule allows for importing, extracting types from and generating .json files.

Example #

// tsconfig.json

{
    "compilerOptions": {
        "module": "commonjs",
        "resolveJsonModule": true,
        "esModuleInterop": true
    }
}

// .ts

import settings from "./settings.json";

settings.debug === true;  // OK
settings.dry === 2;  // Error: Operator '===' cannot be applied boolean and number


// settings.json

{
    "repo": "TypeScript",
    "dry": false,
    "debug": false
}
by: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html
Wring answered 26/11, 2019 at 10:21 Comment(1)
These are proper compilerOptions - work like a charmBanquer
B
9

You can import a JSON file without modifying tsconfig by specifying explicitly that you are importing JSON

import mydata  from './mydataonfile.json' assert { type: "json" };

I know this does not fully answer the question but many people come here to know how to load JSON directly from a file.

Beckybecloud answered 30/12, 2022 at 0:24 Comment(1)
Import assertions are only supported when the '--module' option is set to 'esnext' or 'nodenext'. ts(2821)Cohlette
P
7

Another way to go

const data: {[key: string]: any} = require('./data.json');

This was you still can define json type is you want and don't have to use wildcard.

For example, custom type json.

interface User {
  firstName: string;
  lastName: string;
  birthday: Date;
}
const user: User = require('./user.json');
Plumbago answered 3/4, 2019 at 8:51 Comment(2)
This has nothing to do with the question and is also bad practice.Underestimate
I did this but I'm getting the dates as strings. What should I do to get the proper date objects deserialized from json?Gulosity
C
4

TypeScript 5.3 introduced Import Attributes, formerly known as Import Assertions, which can provide information about the expected format of an imported module at runtime.

Import Attributes ensure that a module is imported with the correct format. That means a .json file that actually contains runnable JavaScript code is definitely being interpreted as JSON.

import colors from "./colors.json" with { type: "json" };
Cataclysm answered 29/11, 2023 at 8:13 Comment(0)
N
0

Enable "resolveJsonModule": true in tsconfig.json file and implement as below code, it's work for me:

const config = require('./config.json');
Nectareous answered 24/10, 2020 at 3:20 Comment(0)
W
0

Note that if you using @kentor ways

Make sure to add these settings in the compilerOptions section of your tsconfig.json (documentation):

You need to add --resolveJsonModule and--esModuleInterop behind tsc command to compile your TypeScript file.

Example: tsc --resolveJsonModule --esModuleInterop main.ts

Weisbart answered 5/6, 2021 at 12:7 Comment(0)
Y
0

in my case I had to change: "include": ["src"] to "include": ["."] in addition to "resolveJsonModule":true because I tried to import manifest.json from the root of the project and not from ./src

Yoko answered 1/2, 2022 at 19:47 Comment(0)
C
0

require is a common way to load a JSON file in Node.js

Clipfed answered 13/2, 2023 at 18:37 Comment(0)
C
0

I tried all suggestions in this thread to-date, including setting resolveJsonModule to true, moduleResolution to "node", and various formats of the include statement (assert/with) and require. I also closed and restarted VSCode between most changes. Nothing here worked.

But from my ./src/index.ts, I did get import metadata from './block.json working and with no other errors, by setting the tsconfig.json include parameter to ["src"], and setting "files":["src/block.json"]. Note that the JSON file is not in the rootDir.

Interestingly (to me anyway) even when the './block.json' was getting the red squigglies, Intellisense on 'metadata' showed all of the properties defined in the JSON. I do not have a declaration of the schema in this project. So VSCode recognized the file and processed its contents even though TS flagged the error.

I don't know if this is version-specific. In this project I have TypeScript 5.3.3, @types/node 20.11.24, and the tsconfig.json below. From rootDir, my src folder has all source, processed by WebPack into ./build. (The reference to ./dist is there in case TypeScript needs a different outDir than WebPack. I can't say this is THE solution for anyone else, or that other solutions might be more elegant for my own purposes or others, only that this configuration absolutely works as A solution for me at this moment.

{
    "compilerOptions": {
        "composite": true,
        "target": "ES2022",
        "lib": ["ES2022", "ES2022.String", "DOM", "DOM.Iterable"],
        "jsx": "react-jsx",
        "useDefineForClassFields": true,
        "module": "ES2022",
        "moduleResolution": "bundler",
        "types": ["node"],
        "resolveJsonModule": true,
        "allowJs": true,
        "checkJs": true,
        "outDir": "./dist",
        "preserveConstEnums": true,
        "isolatedModules": true,
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "noImplicitAny": false,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "strictBindCallApply": true,
        "strictPropertyInitialization": true,
        "noImplicitThis": true,
        "useUnknownInCatchVariables": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitOverride": true,
        "noPropertyAccessFromIndexSignature": true,
        "skipLibCheck": true
    },
    "include": ["src"],
    "files": ["src/block.json"]
}
Cohlette answered 2/3 at 3:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.