Change content of JSON using NodeJS

Tags: ,



I want to edit my JSON file using NodeJS, everything work exept the last write, my JSON file get } at the end.

My code :

function editPackState(request, response) {
  var name = request.params.name,
      state = parseInt(request.params.state, 10);

  fs.readFile('./configs/packs.json', 'utf8', function(err, data) {

    var packs = JSON.parse(data),
        findedPack = _.find(packs.packs, { name: name });

    findedPack.state = (state) ? true : false;

    fs.writeFile('./configs/packs.json', JSON.stringify(packs, null, 4), function() {

      disableAllModules().then(function() {
        activateModules(findedPack.modules).then(function() {
          response.redirect('/_gestiastore');
        });
      });
    });
  });
}
async function disableAllModules() {
  fs.readFile('./configs/modules.json', 'utf8', function(err, data) {
    var modules = JSON.parse(data);
    _.forEach(modules.modules, function(module) {
      module.state = false;
    });
    fs.writeFile('./configs/modules.json', JSON.stringify(modules, null, 4), function() {
      return Promise.resolve();
    });
  });
}
async function activateModules(moduleNames) {
  fs.readFile('./configs/modules.json', 'utf8', function(err, data) {
    var modules = JSON.parse(data);
    _.forEach(modules.modules, function(module) {
      if (moduleNames.includes(module.name)) {
        module.state = true;
      } else {
        module.state = false;
      }
    });
    fs.writeFile('./configs/modules.json', JSON.stringify(modules, null, 4), function() {
      return Promise.resolve();
    });
  });
}

packs.json :

{
  "packs": [
      {
          "name": "Vitrine",
          "state": false,
          "modules": [
              "cms"
          ]
      }
  ]
}

modules.json :

    {
        "modules": [
            {
                "name": "blog",
                "state": false,
                "models": [
                    "ArticleModel",
                    "ArticleCategoryModel"
                ]
            },
            {
                "name": "portfolio",
                "state": false,
                "models": [
                    "ProjectModel",
                    "ProjectCategoryModel"
                ]
            },
            {
                "name": "cms",
                "state": false,
                "models": [
                    "PageModel",
                    "LinkModel",
                    "SectionModel",
                    "SubSectionModel",
                    "FileModel"
                ]
            },
            {
                "name": "mentions",
                "state": false,
                "model": []
            },
            {
                "name": "vehicle",
                "state": false,
                "models": [
                    "VehicleModel",
                    "VehicleBrandModel",
                    "VehiclePhotoModel",
                    "VehicleTypeModel"
                ]
            },
            {
                "name": "contact",
                "state": false,
                "models": []
            },
            {
                "name": "calendar",
                "state": false,
                "models": [
                    "EventModel",
                    "EventCategoryModel"
                ]
            },
            {
                "name": "ecommerce",
                "state": false,
                "models": [
                    "ProductModel",
                    "ProductBrandModel",
                    "ProductCategoryModel",
                    "ProductSupplierModel",
                    "ProductVariationModel",
                    "OrderModel",
                    "OrderProductModel"
                ]
            },
            {
                "name": "emailing",
                "state": false,
                "models": []
            },
            {
                "name": "analytic",
                "state": false,
                "models": []
            },
            {
                "name": "testimonial",
                "state": false,
                "models": [
                    "TestimonialModel"
                ]
            },
            {
                "name": "gallery",
                "state": false,
                "models": [
                    "GalleryModel",
                    "GalleryPhotoModel"
                ]
            },
            {
                "name": "newsletter",
                "state": false,
                "models": [
                    "NewsletterModel"
                ]
            },
            {
                "name": "documentation",
                "state": false,
                "models": [
                    "DocumentationModel"
                ]
            },
            {
                "name": "rgpd",
                "state": false,
                "models": []
            },
            {
                "name": "joboffer",
                "state": false,
                "models": [
                    "JobOfferModel"
                ]
            },
            {
                "name": "coordinate",
                "state": false,
                "models": [
                    "CoordinateModel"
                ]
            },
            {
                "name": "contributor",
                "state": false,
                "models": [
                    "ContributorModel"
                ]
            },
            {
                "name": "form",
                "state": false,
                "models": [
                    "FormModel"
                ]
            },
            {
                "name": "cgv",
                "state": false
            },
            {
                "name": "cashregister",
                "state": false
            },
            {
                "name": "invoice",
                "state": false
            },
            {
                "name": "resacartabo",
                "state": false
            }
        ]
    }

Result of modules.json :

    {
        "modules": [
            {
                "name": "blog",
                "state": false,
                "models": [
                    "ArticleModel",
                    "ArticleCategoryModel"
                ]
            },
            {
                "name": "portfolio",
                "state": false,
                "models": [
                    "ProjectModel",
                    "ProjectCategoryModel"
                ]
            },
            {
                "name": "cms",
                "state": false,
                "models": [
                    "PageModel",
                    "LinkModel",
                    "SectionModel",
                    "SubSectionModel",
                    "FileModel"
                ]
            },
            {
                "name": "mentions",
                "state": false,
                "model": []
            },
            {
                "name": "vehicle",
                "state": false,
                "models": [
                    "VehicleModel",
                    "VehicleBrandModel",
                    "VehiclePhotoModel",
                    "VehicleTypeModel"
                ]
            },
            {
                "name": "contact",
                "state": false,
                "models": []
            },
            {
                "name": "calendar",
                "state": false,
                "models": [
                    "EventModel",
                    "EventCategoryModel"
                ]
            },
            {
                "name": "ecommerce",
                "state": false,
                "models": [
                    "ProductModel",
                    "ProductBrandModel",
                    "ProductCategoryModel",
                    "ProductSupplierModel",
                    "ProductVariationModel",
                    "OrderModel",
                    "OrderProductModel"
                ]
            },
            {
                "name": "emailing",
                "state": false,
                "models": []
            },
            {
                "name": "analytic",
                "state": false,
                "models": []
            },
            {
                "name": "testimonial",
                "state": false,
                "models": [
                    "TestimonialModel"
                ]
            },
            {
                "name": "gallery",
                "state": false,
                "models": [
                    "GalleryModel",
                    "GalleryPhotoModel"
                ]
            },
            {
                "name": "newsletter",
                "state": false,
                "models": [
                    "NewsletterModel"
                ]
            },
            {
                "name": "documentation",
                "state": false,
                "models": [
                    "DocumentationModel"
                ]
            },
            {
                "name": "rgpd",
                "state": false,
                "models": []
            },
            {
                "name": "joboffer",
                "state": false,
                "models": [
                    "JobOfferModel"
                ]
            },
            {
                "name": "coordinate",
                "state": false,
                "models": [
                    "CoordinateModel"
                ]
            },
            {
                "name": "contributor",
                "state": false,
                "models": [
                    "ContributorModel"
                ]
            },
            {
                "name": "form",
                "state": false,
                "models": [
                    "FormModel"
                ]
            },
            {
                "name": "cgv",
                "state": false
            },
            {
                "name": "cashregister",
                "state": false
            },
            {
                "name": "invoice",
                "state": false
            },
            {
                "name": "resacartabo",
                "state": false
            }
        ]
    }} // the second "}" is too much

Answer

Your disableAllModules and activateModules functions return before their respective readFile callback functions are evaluated.

As a result, when you do this:

disableAllModules().then(function() {
  activateModules(findedPack.modules).then(function() {
    response.redirect('/_gestiastore');
  });
});

The following happens:

  1. disableAllModules() is called, returns undefined, which is wrapped in a Promise due to the async keyword being used
  2. The fs.readFile operation starts
  3. The then callback chained on disableAllModules() is run, calling activateModules(…)
  4. There is now a big race between reading the file in disableAllModules, reading the file in activateModules, writing to the file in disableAllModules, and writing to the file in activateModules

As the Node documentation points out:

It is unsafe to use fs.writeFile() multiple times on the same file without waiting for the callback.

This suggests that you can have competing writes to the same file, resulting in corrupted data like you are seeing here. It’s likely only because you are making small modifications that the data is not more significantly broken.

I recommend refactoring to follow this approach:

  1. Read the file
  2. Make all modifications to the data in memory
  3. Write the new data a single time

Provided this script is run infrequently (or at least, never concurrently), this is a simple approach that should avoid many of the pitfalls of concurrency that you are encountering. If it is possible for multiple instances of this script to be running concurrently, you will need a locking mechanism to avoid separate processes writing to the file concurrently (e.g. lockfile).



Source: stackoverflow