0

How to properly write json file only if the file doesn't exist.

fs.exists method is deprecated so I won't use that.

Any idea?

Altiano Gerung
  • 824
  • 2
  • 15
  • 28
  • 2
    Possible duplicate of [Check synchronously if file/directory exists in Node.js](http://stackoverflow.com/questions/4482686/check-synchronously-if-file-directory-exists-in-node-js) – Andrey Popov Mar 17 '16 at 08:08
  • @AndreyPopov - That answer just suggests another method which is subject to race conditions which is exactly why `fs.exists()` was deprecated. It would be better to find a method of solving this issue which is not subject to race conditions. – jfriend00 Mar 17 '16 at 08:36
  • Hm, how do you know that `fs.access` is subject of race conditions? – Andrey Popov Mar 17 '16 at 08:47
  • @AndreyPopov - Because the only way to not be subject to race conditions is to test the file state and open it exclusively in an atomic operation where no other process can get in between those two operations. You can't do that with two separate function calls in a node.js or even in a C++ application because of multi-tasking any other process can be working in the file system between your two function calls. So you need a file system function that guarantees the test and create to be atomic. This has to be done at the OS level. – jfriend00 Mar 17 '16 at 08:53
  • Oh I get it. Thanks for the explanation :) I still think @Altiano needs simplier solution but it's always good to know! – Andrey Popov Mar 17 '16 at 09:02
  • 1
    @AndreyPopov - Well, this race condition issue is why `fs.exists()` was deprecated so it's probably better now to learn how to avoid them even if it might not have been an issue in this specific case. Once you've encapsulated this functionality into a helper function, you can just call it when needed with actually less code than using something like `fs.exists()` or `fs.access()` which are the race condition susceptible alternatives. So, I think best to learn the best way. – jfriend00 Mar 17 '16 at 09:12
  • @AndreyPopov - Actually, the simplest way you could get into this race condition would be to use the cluster module to increase the scalability of your app. If you suddenly started running multiple processes on the same box that used the `if (fs.exists(...) { do something}` logic, then you would suddenly be susceptible to this race condition because you have multiple processes on the same machine checking an `if` condition on a shared resource in a non-atomic way. – jfriend00 Mar 18 '16 at 15:23

2 Answers2

3

You just need to pass the 'wx' flags to fs.writeFile(). This will create and write the file if it does not exist or will return an error if the file already exists. This is supposed to be free of race conditions which fs.exist() and fs.access() are subject to because they don't have the ability to test and create the file in an atomic action that cannot be interrupted by any other process.

Here's an encapsulated version of that concept:

// define version of fs.writeFile() that will only write the file
// if the file does not already exist and will do so without
// possibility of race conditions (e.g. atomically)
fs.writeFileIfNotExist = function(fname, contents, options, callback) {
    if (typeof options === "function") {
        // it appears that it was called without the options argument
        callback = options;
        options = {};
    }
    options = options || {};
    // force wx flag so file will be created only if it does not already exist
    options.flag = 'wx';
    fs.writeFile(fname, contents, options, function(err) {
        var existed = false;
        if (err && err.code === 'EEXIST') {
           // This just means the file already existed.  We
           // will not treat that as an error, so kill the error code
           err = null;
           existed = true;
        }
        if (typeof callback === "function") {
           callback(err, existed);
        }
    });
}

// sample usage
fs.writeFileIfNotExist("myFile.json", someJSON, function(err, existed) {
    if (err) {
        // error here
    } else {
        // data was written or file already existed
        // existed flag tells you which case it was
    }
});

See a description of the flag values you can pass to fs.writeFile() here in the node.js doc.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
1

Incase anyone, or future me, is looking for a shorter version, I'm going to write one now:

1. Require FS

const fs = require('fs');
or
import fs from 'fs';

2. Use it

if (!fs.existsSync('db.json')) fs.writeFileSync(JSON.stringify({
  'hey-there': 'Hello World!',
  peace: 'love'
}));
or
const jsonFile = 'db.json';
const defaultJsonData = JSON.stringify({
  'hey-there': 'Hello World!',
  peace: 'love'
});
if (!fs.existsSync(jsonFile)) fs.writeFileSync(defaultJsonData);
or
const jsonFile = 'db.json';
const defaultJsonData = {
  'hey-there': 'Hello World!',
  peace: 'love'
};
if (!fs.existsSync(jsonFile)) fs.writeFileSync(JSON.stringify(defaultJsonData));

or if you want to also have the data at the end: (like in my case)

const jsonFile = 'db.json';
let jsonData = {};
if (fs.existsSync(jsonFile)) {
  jsonData = JSON.parse(fs.readFileSync(jsonFile));
} else {
  jsonData = {
    'hey-there': 'Hello World!',
    peace: 'love'
  }
  fs.writeFileSync(JSON.stringify(jsonData));
}
Logic:

if the file db.json exist read the string from that file and parse it, else set the json default data and stringify it into the db.json file..

Example:
console.log(jsonData['hey-there']); // Hello World!
console.log(jsonData.peace); // love

Function Example:

function createJsonFile(fileName) {
  if (!fs.existsSync(fileName)) fs.writeFileSync(JSON.stringify({
    'hey-there': 'Hello World!',
    peace: 'love'
  })); // optionally wrap that in try,catch block and return false inside the catch
  return true;
}
or
const defaultJsonData = JSON.stringify({
  'hey-there': 'Hello World!',
  peace: 'love'
});
function createJsonFile(fileName) {
  if (!fs.existsSync(jsonFile)) fs.writeFileSync(defaultJsonData); // optionally wrap that in try,catch block and return false inside the catch
  return true;
}
or
const defaultJsonData = {
  'hey-there': 'Hello World!',
  peace: 'love'
};
function createJsonFile(fileName) {
  if (!fs.existsSync(jsonFile)) fs.writeFileSync(JSON.stringify(defaultJsonData)); // optionally wrap that in try,catch block and return false inside the catch
  return true;
}

Example Usage:

createJsonFile('db.json');

// or if you wrapped the function content in try,catch block and returned false inside the catch

const jsonFile = 'db.json';
if (createJsonFile(jsonFile)) {
  console.log(`Created ${jsonFile}`);
} else {
  console.log(`Failed Creating ${jsonFile}`);
}

Try,Catch Example:

function createJsonFile(fileName) {
  try {
    if (!fs.existsSync(fileName)) fs.writeFileSync(JSON.stringify({
      'hey-there': 'Hello World!',
      peace: 'love'
    }));
  } catch(e) {
    return false;
  }
  return true;
}

or if you want to also have the data at the end using a function:

function createJsonFile(fileName) {
  if (fs.existsSync(jsonFile)) return JSON.parse(fs.readFileSync(jsonFile));
  // else.. (return ^_^)
  const defaultJsonData = {
    'hey-there': 'Hello World!',
    peace: 'love'
  }
  fs.writeFileSync(JSON.stringify(defaultJsonData));
  return defaultJsonData;
}

const jsonFile = 'db.json';
const jsonData = createJsonFile(jsonFile);
console.log(jsonData['hey-there']); // Hello World!
console.log(jsonData.peace); // love

Optionally export it:

module.exports = {
  createJsonFile
}
and import it:
const { createJsonFile } = require('./file-in-same-dir.js'); // you don't have to specify .js

or for esx:

export default {
    createJsonFile
};
and import it:
import whatEverVariableName from './file-in-same-dir.js'; // you don't have to specify .js

whatEverVariableName.createJsonFile('db.json');

// example for variable names I use: db, api, tools

// db.js
import DB from './db';
orr burgel
  • 381
  • 1
  • 6
  • 16