Match values of a key in a nested array of objects to an identifier key using ramda.js

637 views Asked by At

I'm trying to take this array of nested objects and arrays and return a summed score for the identifier of that specific objects score into a new object. Here is the JSON I'm working with:

{
   "AllData" : [ {
        "company" : google,
        "featureData" : [{
           "ScoreTotal" : 10, 
           "featureName": 'test' 
           },{
           "ScoreTotal" : 10, 
           "featureName": 'test2' 
           },
           {
           "ScoreTotal" : 4, 
           "featureName": 'test3' 
           }]
        }, {
        "company" : amazon,
        "featureData" : [{
           "ScoreTotal" : 4, 
           "featureName": 'test' 
           },{
           "ScoreTotal" : 6, 
           "featureName": 'test2' 
           },
           {
           "ScoreTotal" : 3, 
           "featureName": 'test3' 
           }]
        },{
        "company" : facebook,
        "featureData" : [{
           "ScoreTotal" : 4, 
           "featureName": 'test' },
           {
           "ScoreTotal" : 6, 
           "featureName": 'test2' },
           {
           "ScoreTotal" : 2, 
           "featureName": 'test3' 
           }]
        }, 
   }]
}

I'm trying to create an array of objects that have the sum of the score for each unique featureName and the corresponding featureName like this:

[{featureName: 'test1', summedScore: '18'}, {featureName: 'test2', summedScore: '22'},{featureName: 'test3', summedScore: '9'}]

A solution that was close to what I was looking for can be found here, but matching the identifier with the sum was not shown in the solution.

Thank you in advance!

3

There are 3 answers

0
customcommander On BEST ANSWER

Here's a solution with Ramda

const data = {
  "AllData": [{
    "company": "google",
    "featureData": [{
      "ScoreTotal": 10,
      "featureName": 'test'
    }, {
      "ScoreTotal": 10,
      "featureName": 'test2'
    },
    {
      "ScoreTotal": 4,
      "featureName": 'test3'
    }]
  }, {
    "company": "amazon",
    "featureData": [{
      "ScoreTotal": 4,
      "featureName": 'test'
    }, {
      "ScoreTotal": 6,
      "featureName": 'test2'
    },
    {
      "ScoreTotal": 3,
      "featureName": 'test3'
    }]
  }, {
    "company": "facebook",
    "featureData": [{
      "ScoreTotal": 4,
      "featureName": 'test'
    },
    {
      "ScoreTotal": 6,
      "featureName": 'test2'
    },
    {
      "ScoreTotal": 2,
      "featureName": 'test3'
    }]
  }
  ]
};

const getScores =
  R.pipe(
    // Group all featureData objects into a single array
    R.prop('AllData'),
    R.map(R.prop('featureData')),
    R.unnest,
    
    // Group all featureData objects with the same featureName into separate arrays
    R.groupBy(R.prop('featureName')),
    
    // Merge all objects in each array by summing their `ScoreTotal` properties
    R.map(R.reduce(R.mergeWithKey((key, left, right) => key === 'ScoreTotal' ? left + right : right), {})),
    R.values,
    
    // Reshape each object
    R.map(R.applySpec({
      featureName: R.prop('featureName'),
      summedScore: R.prop('ScoreTotal')
    })));

console.log(

  getScores(data)

);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>

0
Orelsanpls On

I'm creating an object where I use the name of the feature as a key, so it's easy to create theirs total score. Then I turn the object into an array matching your expectation.

const json = {
  "AllData": [{
      "company": 'google',
      "featureData": [{
          "ScoreTotal": 10,
          "featureName": 'test'
        }, {
          "ScoreTotal": 10,
          "featureName": 'test2'
        },
        {
          "ScoreTotal": 4,
          "featureName": 'test3'
        }
      ]
    }, {
      "company": 'amazon',
      "featureData": [{
          "ScoreTotal": 4,
          "featureName": 'test'
        }, {
          "ScoreTotal": 6,
          "featureName": 'test2'
        },
        {
          "ScoreTotal": 3,
          "featureName": 'test3'
        }
      ]
    }, {
      "company": 'facebook',
      "featureData": [{
          "ScoreTotal": 4,
          "featureName": 'test'
        },
        {
          "ScoreTotal": 6,
          "featureName": 'test2'
        },
        {
          "ScoreTotal": 2,
          "featureName": 'test3'
        }
      ]
  }]
};

const obj = json.AllData.reduce((tmp, x) => {
  x.featureData.forEach((y) => {
    tmp[y.featureName] = (tmp[y.featureName] || 0) + y.ScoreTotal;
  });

  return tmp;
}, {});

const arr = Object.keys(obj).map(x => ({
  summedScore: obj[x],
  featureName: x,
}));

console.log(arr);

0
Scott Sauyet On

I like to work out transformations like this a step at a time, starting with the original data, and working through to a final format. I can do this inside Ramda's REPL with a pipe call, adding the individual calls one-by-one and checking that the results are moving toward what I want.

Doing so, this is the answer I come up with.

const {pipe, prop, pluck, unnest, groupBy, map, sum, toPairs, zipObj} = R

const sumByFeatureName = pipe(
  prop('AllData'),
  pluck('featureData'),
  unnest,
  groupBy(prop('featureName')),
  map(pluck('ScoreTotal')),
  map(sum),
  toPairs,
  map(zipObj(['featureName', 'summedScore'])),
)

const json = {"AllData": [{"company": "google", "featureData": [{"ScoreTotal": 10, "featureName": "test"}, {"ScoreTotal": 10, "featureName": "test2"}, {"ScoreTotal": 4, "featureName": "test3"}]}, {"company": "amazon", "featureData": [{"ScoreTotal": 4, "featureName": "test"}, {"ScoreTotal": 6, "featureName": "test2"}, {"ScoreTotal": 3, "featureName": "test3"}]}, {"company": "facebook", "featureData": [{"ScoreTotal": 4, "featureName": "test"}, {"ScoreTotal": 6, "featureName": "test2"}, {"ScoreTotal": 2, "featureName": "test3"}]}]}

console.log(sumByFeatureName(json))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

This demonstrates the structure of all the intermediate results:

const sumByFeatureName = pipe(
  prop('AllData'),                              //=> [{company: "google", featureData: [{ScoreTotal: 10, featureName: "test"}, ...], {company: 'amazon', ...}, ...]
  pluck('featureData'),                         //=> [[{ScoreTotal: 10, featureName: 'test"}, {ScoreTotal: 10, featureName: 'test2'}, ...] [...], ...]
  unnest,                                       //=> [{ScoreTotal: 10, featureName: 'test'}, {ScoreTotal: 10, featureName: 'test2'}, ...]  
  groupBy(prop('featureName')),                 //=> {test: [{ScoreTotal: 10, featureName: 'test'}, ...], test2: [{...}, ...]}
  map(pluck('ScoreTotal')),                     //=> {test: [10, 4, 4], test2: [10, 6, 6], test3: [4, 3, 2]} 
  map(sum),                                     //=> {test: 18, test2: 22, test3: 9}
  toPairs,                                      //=> [['test', 18], ['test2', 22], ['test3', 9]]
  map(zipObj(['featureName', 'summedScore'])),  //=> [{featureName: 'test, summedScore: 19}, ...] 

)

Note that if we drop off the last two lines (toPairs and map(zipObj(...))), we get this format:

{test: 18, test2: 22, test3: 9}

which is often a more useful structure than your requested output.

And also note that this sort of work helps one identify useful functions one might want in our own utility library, or even to propose as additions to a library like Ramda. If I've done that toPairs/map(zipObj(...)) shuffle enough times, I might consider writing my own little function for it, such as:

const objArray = (keyName, valName) => vals => 
  map(zipObj([keyName, valName]), toPairs(vals))

and then replace those two lines with objArray('featureName', 'summedScore').