Code Review Videos > JavaScript & TypeScript > How I Fixed: Needs an import assertion of type JSON

How I Fixed: Needs an import assertion of type JSON

Whilst updating an older Express project to use TypeScript, I came across a file where I was pulling in a JSON file directly using require. I wanted to remove the require usage and switch to import, but this was not quite as straightforward as I initially expected.

It turns out that this can be demonstrated really easily with just a single JS file, and the package.json that you would almost certainly have in any JS project. So the setup for this example is going to be super simple.

cd /tmp
mkdir node-json-import-test
cd node-json-import-test
npm init -y

{
  name: 'json-import-test',
  version: '1.0.0',
  description: '',
  main: 'index.js',
  scripts: { test: 'echo "Error: no test specified" && exit 1' },
  author: '',
  license: 'ISC'
}Code language: Shell Session (shell)

Right so to quickly recap, create a new directory called node-json-import-test and run npm init accepting all the defaults (-y) which then spits out the resulting package.json contents to the console.

We can then create our index.js file and get cracking:

touch index.jsCode language: Shell Session (shell)

If you’re using require syntax, the way to load a JSON file is actually super easy. Just require it:

// index.js

const packageJson = require('./package.json');

console.log(packageJson);
console.log(packageJson.name);Code language: JavaScript (javascript)

And then run the script:

➜  node-json-import-test node index.js
{
  name: 'json-import-test',
  version: '1.0.0',
  description: '',
  main: 'index.js',
  scripts: { test: 'echo "Error: no test specified" && exit 1' },
  author: '',
  license: 'ISC'
}
json-import-testCode language: JavaScript (javascript)

Which logs out the full package.json contents as expected, and then the specific value of the given key.

That works really nicely, and away you go.

However, there isn’t (at the time of writing) a direct swap to an equivalent when using import:

// won't work

import importedPackageJson from './package.json';
console.log(importedPackageJson);
console.log(importedPackageJson.name);Code language: JavaScript (javascript)

If we run this we get an error:

➜  node-json-import-test node index.js
(node:3272544) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/home/chris/Development/node-json-import-test/index.js:7
import importedPackageJson from './package.json';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:360:18)
    at wrapSafe (node:internal/modules/cjs/loader:1088:15)
    at Module._compile (node:internal/modules/cjs/loader:1123:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1213:10)
    at Module.load (node:internal/modules/cjs/loader:1037:32)
    at Module._load (node:internal/modules/cjs/loader:878:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:23:47

Node.js v18.11.0
Code language: Shell Session (shell)

OK, so a couple of problems to address.

The actual problem here is:

SyntaxError: Cannot use import statement outside a moduleCode language: Shell Session (shell)

But the error message is also kind enough to tell us exactly how to fix this:

Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.Code language: Shell Session (shell)

I’m going with the “set “type”: “module” in the package.json” suggestion:

{
  "name": "json-import-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
Code language: JSON / JSON with Comments (json)

Now, without changing the index.js contents, this should all start to work, right?

➜  node-json-import-test node index.js
node:internal/errors:484
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "file:///home/chris/Development/node-json-import-test/package.json" needs an import assertion of type "json"
    at new NodeError (node:internal/errors:393:5)
    at validateAssertions (node:internal/modules/esm/assert:82:15)
    at defaultLoad (node:internal/modules/esm/load:84:3)
    at nextLoad (node:internal/modules/esm/loader:163:28)
    at ESMLoader.load (node:internal/modules/esm/loader:605:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:457:22)
    at new ModuleJob (node:internal/modules/esm/module_job:63:26)
    at #createModuleJob (node:internal/modules/esm/loader:480:17)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:434:34)
    at async ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:78:21) {
  code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING'
}

Node.js v18.11.0Code language: Shell Session (shell)

Hmm, not quite.

But again, the error message here is pretty helpful, if a little scary looking:

TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "file:///tmp/node-json-import-test/package.json" needs an import assertion of type "json"Code language: Shell Session (shell)

This was the new thing I discovered, and what led to this post:

needs an import assertion of type "json"Code language: Shell Session (shell)

What Are Import Assertions In NodeJS?

Import assertions are a feature introduced in ECMAScript 2021 (ES12) that allow developers to specify additional metadata about the modules being imported. These assertions are available in Node.js versions 14.0.0 and higher.

With import assertions, you can specify metadata about the module being imported, such as its integrity checksum or its media type. The syntax for import assertions is as follows:

import defaultExport from 'module' assert { assertion };
import * as namedExports from 'module' assert { assertion };
import { namedExport } from 'module' assert { assertion };Code language: JavaScript (javascript)

Here, assertion is an object that specifies the metadata for the imported module. It can have one or more properties that define the metadata. For example:

import { foo } from './module.js' assert {
  type: 'json',
  integrity: 'sha384-ABC123'
};Code language: JavaScript (javascript)

In this example, the import statement includes an assertion object that specifies that the module being imported is a JSON file and includes a SHA384 integrity checksum.

Import assertions can help improve the security and reliability of your code by providing additional checks and metadata about the modules being imported.

Using Import Assertions In NodeJS To Import JSON

Armed with this knowledge, we can add the required import assertion to our import statement and get a working solution (with a caveat):

➜  node-json-import-test node index.js
{
  name: 'json-import-test',
  version: '1.0.0',
  description: '',
  main: 'index.js',
  type: 'module',
  scripts: { test: 'echo "Error: no test specified" && exit 1' },
  author: '',
  license: 'ISC'
}
json-import-test
(node:3274350) ExperimentalWarning: Importing JSON modules is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)Code language: Shell Session (shell)

A little bit easier to see in the terminal output:

nodejs import assertion warning

So it works nicely, but it comes with a warning because this feature is experimental.

If you don’t want the warning, there are two further links that offer explanations and work arounds:

For me, I could accept the experimental warning so it was fine.

What is the Difference Between require and import in nodejs?

In Node.js, both require and import are used to include code from other files or modules in your JavaScript code. However, there are some differences between them:

  1. Syntax: require is a function that takes a string argument, while import is a statement that uses the ES6 module syntax.
  2. Scope: require is used to load modules using CommonJS syntax, which creates a new scope for each module. On the other hand, import creates a lexical scope and only imports the bindings specified in the statement.
  3. Asynchronous vs. Synchronous: require is a synchronous function, meaning that it blocks the execution until the required module is loaded. On the other hand, import is an asynchronous operation and it doesn’t block the execution of the program.
  4. Compatibility: require is the standard way to load modules in Node.js, while import is part of the ES6 module specification and is supported by some versions of Node.js (v13.2.0 or higher) and modern browsers.

In summary, require is used for synchronous module loading using CommonJS syntax, while import is used for asynchronous module loading using ES6 module syntax.

1 thought on “How I Fixed: Needs an import assertion of type JSON”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.