javascriptnode.js

"SyntaxError: Unexpected identifier 'assert'" on JSON import in Node v22


A Node program of mine that worked fine in Node v21 now immediately crashes with a syntax error in Node v22. The line that triggers the error looks like this:

import config from "./some-config-file.json" assert { type: "json" };

and the error says:

SyntaxError: Unexpected identifier 'assert'
    at compileSourceTextModule (node:internal/modules/esm/utils:337:16)
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:166:18)
    at callTranslator (node:internal/modules/esm/loader:436:14)
    at ModuleLoader.moduleProvider (node:internal/modules/esm/loader:442:30)
    at async ModuleJob._link (node:internal/modules/esm/module_job:106:19)

Why am I getting this error in code that previously worked, and how should I fix it?


Solution

  • This is due to support for "import assertions" being removed from Node in favour of "import attributes", which are essentially the same thing except you have to use the keyword with instead of assert:

    import config from "./some-config-file.json" with { type: "json" };
    

    (That trivial explanation of the change might sound like it must be missing something, but at least for now and in the context of Node, it really isn't. The change happened in Node because the underlying ECMAScript spec proposal made the same name and keyword change, on the grounds that some new import attributes - besides type: "json" - will have behaviours not properly described as "asserting" anything. But Node still supports only one import attribute, type: "json", which behaves exactly the same as it did with the old assert syntax.)

    To fix the error, you could just adopt the new syntax (i.e. change assert to with). However, do note that support for the "import attributes" syntax (with with) was added later than the older "import assertions" syntax (with assert); the latter has been around since Node 17.1 and 16.14, while the former was only added in Node 18.20. Thus, swapping from assert to with will fix your program for Node 22 and later at the expense of breaking it for Node 16 and 17.

    If avoiding breaking compatibility with old Node versions matters to you, consider not using import for reading JSON files at all and instead reading and parsing them explicitly:

    import fs from 'node:fs';
    import { dirname, join } from 'node:path';
    import { fileURLToPath } from 'node:url';
    
    const configPath = join(
      dirname(fileURLToPath(import.meta.url)),
      './some-config-file.json'
    );
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    

    This is, of course, uglier and more complicated, in part because just getting the path to the current ES module in a way compatible with old Node versions is itself annoyingly complicated.

    The behaviour is also not quite identical to import. If you were previously importing the same JSON module from multiple different JavaScript modules in your program, import would only read and parse the JSON file once and then cache the module in memory to return on subsequent imports, whereas the incantation I propose above won't do this. This makes my version worse for performance and also more vulnerable to silly behaviours if the content of the JSON file gets modified under your program's feet while it's launching. To fix this, you could always wrap the parsing of the JSON file in a single JavaScript module that all your other modules import the parsed JSON from.

    But in exchange for accepting the ugliness, verbosity, and slightly worse behaviour than import, you get broad compatibility across Node versions, which might be worth it for you!