How to parse a file with array of JSON objects using Node.js?

Viewed 111

I have this project where I want to get the JSON data from a file with array of json objects with repetitive attributes:

some-file: (each object is in 1 line, for simplicity I formatted it)

{
   "A":"ALL",
   "B":"3349256522",
   "Location":{

      "Country":"USA"
   },
   "EffectiveDate":"2020-03-04T14:15:52.063Z",
   "Demographic":{
      "Q":"done",
      "G":"ok",
      "AppVersion":"1.3.4",
   },
   "ApplicationId":"92398723892937",
   "Id":"23232993939333",
   "CreationDate":"2020-03-04T14:15:52.063Z"
}

{
   "A":"NONE",
   "B":"8469256522",
   "Location":{
      "Country":"SPAIN"
   },
   "EffectiveDate":"2020-03-04T14:15:52.063Z",
   "Demographic":{
      "Q":"done",
      "G":"ok",
      "AppVersion":"1.3.5",
   },

   "ApplicationId":"92398723892937",
   "Id":"23232993939333",
   "CreationDate":"2020-03-09T14:15:52.063Z"
}

{
   "A":"ALL",
   "B":"8469256522",
   "Location":{

      "Country":"USA"
   },
   "EffectiveDate":"2020-03-04T14:15:52.063Z",
   "Demographic":{
      "Q":"done",
      "G":"ok",
      "AppVersion":"1.3.4",
   },

   "ApplicationId":"92398723892937",
   "Id":"23232993939333",
   "CreationDate":"2020-03-11T14:15:52.063Z"
}

I would like to get some statistics for specific items, Country and AppVersion. How can I process it using NodeJs to produce this output:

{
   "stats":{
      "Country":{
         "USA":"2",
         "SPAIN":"1"
      },
      "AppVersion":{
         "1.3.4":"2",
         "1.3.5":"1"
      }
   }
}

Also, the input file 'some-file' is not a valid JSON file, any recommendation how to convert it to be valid (in code) ?

The original file is (1 line each object):
{   "A":"ALL",   "B":"3349256522",   "Location":{      "Country":"USA"   },   "EffectiveDate":"2020-03-04T14:15:52.063Z",   "Demographic":{      "Q":"done",      "G":"ok",      "AppVersion":"1.3.4"   },   "ApplicationId":"92398723892937",   "Id":"23232993939333",   "CreationDate":"2020-03-04T14:15:52.063Z"}
{   "A":"ALL",   "B":"3349256522",   "Location":{      "Country":"SPAIN"   },   "EffectiveDate":"2020-03-04T14:15:52.063Z",   "Demographic":{      "Q":"done",      "G":"ok",      "AppVersion":"1.3.5"   },   "ApplicationId":"92398723892937",   "Id":"23232993939333",   "CreationDate":"2020-03-04T14:15:52.063Z"}
{   "A":"ALL",   "B":"3349256522",   "Location":{      "Country":"ITALY"   },   "EffectiveDate":"2020-03-04T14:15:52.063Z",   "Demographic":{      "Q":"done",      "G":"ok",      "AppVersion":"1.3.4"   },   "ApplicationId":"92398723892937",   "Id":"23232993939333",   "CreationDate":"2020-03-04T14:15:52.063Z"}
3 Answers

You can use array.reduce to accumulate the count values:

let input = [{
   "A":"ALL",
   "B":"3349256522",
   "Location":{
      "Country":"USA"
   },
   "EffectiveDate":"2020-03-04T14:15:52.063Z",
   "Demographic":{
      "Q":"done",
      "G":"ok",
      "AppVersion":"1.3.4",
   },
   "ApplicationId":"92398723892937",
   "Id":"23232993939333",
   "CreationDate":"2020-03-04T14:15:52.063Z"
},
{
   "A":"NONE",
   "B":"8469256522",
   "Location":{
      "Country":"SPAIN"
   },
   "EffectiveDate":"2020-03-04T14:15:52.063Z",
   "Demographic":{
      "Q":"done",
      "G":"ok",
      "AppVersion":"1.3.5",
   },

   "ApplicationId":"92398723892937",
   "Id":"23232993939333",
   "CreationDate":"2020-03-09T14:15:52.063Z"
},
{
   "A":"ALL",
   "B":"8469256522",
   "Location":{

      "Country":"USA"
   },
   "EffectiveDate":"2020-03-04T14:15:52.063Z",
   "Demographic":{
      "Q":"done",
      "G":"ok",
      "AppVersion":"1.3.4",
   },

   "ApplicationId":"92398723892937",
   "Id":"23232993939333",
   "CreationDate":"2020-03-11T14:15:52.063Z"
}];

let result = input.reduce((acc,current) => {
    let c = current.Location.Country;
    let v = current.Demographic.AppVersion;
    if(acc.Country[c]){
        acc.Country[c]++;
    } else {
        acc.Country[c] = 1;
    }
    
    if(acc.AppVersion[v]){
        acc.AppVersion[v]++;
    } else {
        acc.AppVersion[v] = 1;
    }
    return acc;
}, {Country: {}, AppVersion: {}});

console.log({ stats: result });

First, your JSON is not valid and throws some errors so I fixed it a bit (did it very quick so the formatting is bad but at least Node can read it):

{
"data": [
{
   "A":"ALL",
   "B":"3349256522",
   "Location":{

      "Country":"USA"
   },
   "EffectiveDate":"2020-03-04T14:15:52.063Z",
   "Demographic":{
      "Q":"done",
      "G":"ok",
      "AppVersion":"1.3.4"
   },
   "ApplicationId":"92398723892937",
   "Id":"23232993939333",
   "CreationDate":"2020-03-04T14:15:52.063Z"
},

{
   "A":"NONE",
   "B":"8469256522",
   "Location":{
      "Country":"SPAIN"
   },
   "EffectiveDate":"2020-03-04T14:15:52.063Z",
   "Demographic":{
      "Q":"done",
      "G":"ok",
      "AppVersion":"1.3.5"
   },

   "ApplicationId":"92398723892937",
   "Id":"23232993939333",
   "CreationDate":"2020-03-09T14:15:52.063Z"
},

{
   "A":"ALL",
   "B":"8469256522",
   "Location":{

      "Country":"USA"
   },
   "EffectiveDate":"2020-03-04T14:15:52.063Z",
   "Demographic":{
      "Q":"done",
      "G":"ok",
      "AppVersion":"1.3.4"
   },

   "ApplicationId":"92398723892937",
   "Id":"23232993939333",
   "CreationDate":"2020-03-11T14:15:52.063Z"
}
]
}

Next, as for reading it in Node, I recommend fs-extra over the default fs module. Install with npm i fs-extra and in your code:

const fs = require('fs-extra');

// DON'T use Synchronous methods in production code. it is fine for learning however.
// I also named your json file "info.json"

let data = fs.readJsonSync('info.json');

let stats = {
    country: {},
    version: {}
};

// This iterates over each array element
data.data.forEach((entry) => {
    // let is similar to var, but not as global as var can be
    let country = entry.Location.Country;

    // First check if the country already exists in the stats. If not we can create it.
    // hasOwnProperty lets you check if an object already has a certain key
    if (!stats.country.hasOwnProperty(country)) stats.country[country] = 0;

    // Increase the counter on the country
    stats.country[country] += 1;

    // Repeat the above lines for demographic/appversion
});

console.log(stats); // returns: { country: { USA: 2, SPAIN: 1 }, version: {} }

I wrote this super quick so there may be issues. Let me know if it works for you!

Since your file is a collection of JSON documents back-to-back, all on one line, you can break it apart pretty simply. Something like:

const fs = require('fs-extra'); // to get async file functions. You can also use regular 'fs' with a callback

const yourFilePath = 'some/path.txt';

async function convertFileToArray(filepath){
  const rawContent = await fs.readFile(filepath,'utf8');

  // EDIT: The following is if there were *no* newlines in the file
  // const asJson = `[${ rawContent.replace(/\}\s*\{/g,'},{')}]`;

  // This is what you want if you nave newlines between each object:
  const asJson = `[${rawContent.split(/[\r\n]+/g).join(',')}]`;
  const asArray = JSON.parse(asJson);
  return asArray;
}

async function main(){
  const dataArray = await convertFileToArray(yourFilePath);
  // Process using something like the answers provided by others
}

Edit: Noticed after posting that you have newlines separating each object. Updated code to reflect that.

Related