In this blog post, we examine the ECMAScript proposal “JSON modules” (by Sven Sauleau, Daniel Ehrenberg, Myles Borins, and Dan Clark). It lets us import JSON data as if it were an ECMAScript module.
Various bundlers (such as webpack) have allowed us to import JSON data as if it were an ECMAScript module for a long time. JSON modules turn this into a standard feature.
Why is that interesting? It provides a convenient way of using, e.g., configuration data in our apps. Take, for example, the following file structure:
my-app/
src/
config-data.json
main.mjs
my-app/src/config-data.json
looks as follows:
{
"appName": "My App"
}
This is my-app/src/main.mjs
:
import configData from './config-data.json' assert {type: 'json'};
console.log(`I am ${configData.appName}!`);
The syntax from assert
until the end is called an import assertion. JSON modules were one of the use cases for which import assertions were created.
The default export of a JSON module contains the JSON data. There are no named exports.
fetch()
Without JSON modules, we would have to use fetch()
:
async function fetchConfigData(relativePath) {
const urlOfConfigData = new URL(
relativePath, import.meta.url); // (A)
const response = await fetch(urlOfConfigData.toString()); // (B)
const json = await response.json(); // (C)
return json;
}
const configData = await fetchConfigData('config-data.json');
console.log(`I am ${configData.appName}!`);
We are using two relatively new features:
import.meta.url
(line A).fetch()
has two downsides compared to JSON modules:
fetch()
. (And I suspect JSON modules will be supported sooner than fetch()
.)import()
The previous import
statement was static (fixed at runtime). We can also import JSON modules dynamically (changeably at runtime):
async function importConfigData(moduleSpec) {
const namespaceObj = await import( // (A)
moduleSpec, {assert: {type: 'json'}});
return namespaceObj.default; // (B)
}
const configData = await importConfigData('./config-data.json');
console.log(`I am ${configData.appName}!`);
Note that the import()
operator (line A) returns a module namespace object. That is why we return the value of property .default
(which contains the default export) in line B.
You may wonder why we have to use extra syntax at the end of the important statement:
import configData from './config-data.json' assert {type: 'json'};
Why can’t JavaScript detect that this is JSON by looking at the filename extension?
import configData from './config-data.json';
This is not possible because it can cause security issues: Browsers never look at filename extensions, they look at content types. And servers are responsible for providing content types for files. Therefore, two things can happen when importing a .json
file:
application/json
and the importing would go wrong in some manner.text/javascript
, it could execute code inside our app.Therefore, JavaScript won’t rely on content types when importing JSON.