Skip to main content
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Reem Jaghlit
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Reem Jaghlit

Node's require() Function Can Seamlessly Switch Between .json And .json.js Files

By
Published in Comments (2)

Last year, I wrote a quick blog post about Node.js' ability to read in JSON (JavaScript Object Notation) files using the require() function. This feature, which I use all the time, is an awesome way to synchronously read in configuration or migration data. But, sometimes, the JSON specification is a little too constrictive. In those cases, a JavaScript module gives you a lot more wiggle room; and, the elegant part is, you can seamlessly switch between JSON and JavaScript files without having to change any of your require statements.

This technique works because the .js file extension is optional in a Node.js require statement. As such, if you require() a file called "data.json", Node.js will look for each of the following filenames, with the given order of precedence:

  • data.json
  • data.json.js

What this means is that you can start out with "data.json"; then, if you find that you need to switch over to a JavaScript module for more flexibility, you can rename the file to "data.json.js" and your code will continue to work properly.

To see this in action, I created a small test file that reads-in and logs-out some require()'d data:

// Synchronously read in this JSON data file.
var migration = require( "./migration.json" );

console.log( "Users Configuration:" );
console.log( migration.users );

As you can see, we're starting out by reading in a file called, "migration.json":

{
	"users": "SELECT * FROM `user` WHERE isActive = 1"
}

When we run this, we get the following terminal output:

Using Node.js to read in JSON data using require().

As you can see, we successfully read-in and parsed the JSON data using require().

Now, let's say that we want to make our embedded SQL statement a little more readable with a multi-line template string. Unfortunately, the JSON specification doesn't allow for this (or, at the very least, Node.js doesn't parse it properly). So, we're going to rename the file from "migration**.json**" to "migration**.json.js**" and add the appropriate exports and formatting:

module.exports = {
	users:
	`
		SELECT
			*
		FROM
			user
		WHERE
			isActive = 1
	`
};

This is much easier to read and maintain. And, if we run the original import code, without any modifications, we get the following terminal output:

Using Node.js to require JavaScript

As you can see, Node.js seamlessly switched from the ".json" file to the ".json.js" file. We didn't have to to change our require() statement at all.

At first, you might think that this is a misleading approach. But, I posit that the use of the ".json" extension in the require() statement demonstrates the intent of the file. Meaning, even when we switch over to a ".json.js" file, the intent of the JavaScript module is still to provide "data", not functionality. I like to think of it like an Interface or a contract - the ".json.js" file is "implementing" the ".json interface".

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

Thanks for very useful article on CommonJS and node.js import system. It would be nice if the limitations of require()'s magic pertaining to extension inference are also called out.

For instance, given the input:

{"nested":"{\"sub\":\"{\\\"val\\\":7}\"}"}

if the file has no extension, we get a syntax exception upon require('./test'):

(function (exports, require, module, __filename, __dirname) { {"nested":"{\"sub\":\"{\\\"val\\\":7}\"}"}

if the .json file extension is added, the error disappears:

JSON.parse ( JSON.parse ( require ( './test.json' ).nested ).sub ).val

// => 7

I don't blame the disambiguation heuristics, but require.resolve could have provided with an overload with additional parameter `treatAs` or require.extensions array (which is deprecated) could have replaced with better alternative which had handled support extension-less cases.. but this is too late now as per node.js docs:

> Since the Module system is locked, this feature will probably never go away.

(https://nodejs.org/api/globals.html#globals_require_extensions)

Hence I ended up using good old fs.readFileSync followed by try-catch'd JSON.parse. Another (hacky) option was to fs.rename (twice) to add extension and revert the filename.

15,811 Comments

@Adeel,

Sorry, I'm a little bit confused on the name of the file. Are you saying that the file was "test.json" and you were requiring just "test" in the original case? Yeah, in that case, I'm pretty sure it wouldn't know where to look because, as you are saying, ".json" isn't one of the file extensions that it automatically tries to look for.

But, if you're going to go through the trouble of doing a fileReadSync(), why not just add the file-extension to the require() statement?

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel