Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Jay Vanderlyn and Mike Shea and Graeme Rae
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Jay Vanderlyn , Mike Shea@sheaman ) , and Graeme Rae@graemerae )

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

By Ben Nadel on

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".




Reader 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.

Reply to this Comment

@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?

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.