5

I have an object array (coming from an XLSX.js parser, so its length and contents vary) representing grants that have been given to projects.

Simplified, it looks something like this:

var grants = [
    { id: "p_1", location: "loc_1", type: "A", funds: "5000" },
    { id: "p_2", location: "loc_2", type: "B", funds: "2000" },
    { id: "p_3", location: "loc_3", type: "C", funds:  "500" },
    { id: "p_2", location: "_ibid", type: "D", funds: "1000" },
    { id: "p_2", location: "_ibid", type: "E", funds: "3000" }
];

I need to merge these into a new array that will look like this:

var projects = [
    { id: "p_1", location: "loc_1", type: "A", funds: "5000" },
    { id: "p_2", location: "loc_2", type: ["B", "D", "E"], funds: ["2000", "1000", "3000"] },
    { id: "p_3", location: "loc_3", type: "C", funds: "500" }
];

... so that when the id is the same, it will merge the objects and combine some of their key values (in the example type and funds) into a simple sub-array. The other keys (location) in these merged objects inherit the values from the first instance and ignore the rest.

After several failed attempts and a lot of searching online, I got an idea from this answer to loop through grants like this:

var res = {};

$.each(grants, function (key, value) {
    if (!res[value.id]) {
        res[value.id] = value;    
    } else {
        res[value.id].type = [res[value.id].type, value.type];
        res[value.id].funds = [res[value.id].funds, value.funds];
    }
});

var projects = []
projects = $.map( res, function (value) { return value; } );

It actually works perfectly, EXCEPT that as I need an array, I removed .join(',') from the ends (from the answer mentioned above), which in turn has created the problem I can't seem to solve now. The sub-arrays become nested in each other somehow if there is at least three items in them! I sort of understand why (the loop), but I wonder if there is a way to convert all these little multi-dimensional arrays inside the objects into sigle arrays (like: type: ["B", "D", "E"] )?

var grants = [
    { id: "p_1", location: "loc_1", type: "A", funds: "5000" },
    { id: "p_2", location: "loc_2", type: "B", funds: "2000" },
    { id: "p_3", location: "loc_3", type: "C", funds:  "500" },
    { id: "p_2", location: "_ibid", type: "D", funds: "1000" },
    { id: "p_2", location: "_ibid", type: "E", funds: "3000" }
];

var res = {};

$.each(grants, function (key, value) {
    if (!res[value.id]) {
        res[value.id] = value;    
    } else {
        res[value.id].type = [res[value.id].type, value.type];
        res[value.id].funds = [res[value.id].funds, value.funds];
    }
});

var projects = []
projects = $.map( res, function (value) { return value; } );


$("pre").html(JSON.stringify(projects,null,2));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre id="json"></pre>
Community
  • 1
  • 1
tmrk
  • 63
  • 1
  • 7

4 Answers4

2

Would this be an idea?

var grants = [
    { id: "p_1", location: "loc_1", type: "A", funds: "5000" },
    { id: "p_2", location: "loc_2", type: "B", funds: "2000" },
    { id: "p_3", location: "loc_3", type: "C", funds:  "500" },
    { id: "p_2", location: "_ibid", type: "D", funds: "1000" },
    { id: "p_2", location: "_ibid", type: "E", funds: "3000" }
];
var joined = [];

// map and push to joined
grants.map(
  function (v) {
    if (!(v.id in this)) {
      this[v.id] = v;
      joined.push(v);
    } else {
      var current = this[v.id];
      current.type = [v.type].concat(current.type);
      current.funds = [v.funds].concat(current.funds);
    }
  }, {}
);

// show it
document.querySelector('#result').textContent =
   JSON.stringify(joined, null, ' ');
<pre id="result"></pre>
KooiInc
  • 119,216
  • 31
  • 141
  • 177
  • Thanks! This also works well. Although you also included "location" in the code which I don't need to get stacked in an array, but I just removed that line, and it gives the desired result. I saved it here: http://jsfiddle.net/kqtj315e/ – tmrk Sep 26 '15 at 12:11
  • Glad I could be of assistance. I realized the mapping/joining could be simplified considerably (see edited answer for that) – KooiInc Sep 26 '15 at 13:05
  • After trying all the answers here, I actually decided to go with this method, because with all the rest of my code this produces the simplest logic that also turned out to be the shortest. (However, there's another problem I faced http://stackoverflow.com/questions/32806786/removing-empty-object-keys-in-an-array-of-objects :) ) – tmrk Sep 27 '15 at 10:10
1

You can just change these lines:

 res[value.id].type = [res[value.id].type, value.type];
 res[value.id].funds = [res[value.id].funds, value.funds];

To this:

Array.isArray(res[value.id].type) ? res[value.id].type.push(value.type) : res[value.id].type = [res[value.id].type, value.type];
Array.isArray(res[value.id].funds) ? res[value.id].funds.push(value.funds) : res[value.id].funds = [res[value.id].funds, value.funds];
Saar
  • 2,276
  • 1
  • 16
  • 14
  • Thank you so much, this is exactly what I wanted! Elegant and quick. (For future reference, I saved the full code in a fiddle: http://jsfiddle.net/3g444sy9/ ) – tmrk Sep 26 '15 at 11:56
1

I propose this solution.

It features a look up, if the index exist in the project array, if so it pushes the type and funds if not then the type and funds properties are changed to array with the value as first element.

var grants = [
        { id: "p_1", location: "loc_1", type: "A", funds: "5000" },
        { id: "p_2", location: "loc_2", type: "B", funds: "2000" },
        { id: "p_3", location: "loc_3", type: "C", funds: "500" },
        { id: "p_2", location: "_ibid", type: "D", funds: "1000" },
        { id: "p_2", location: "_ibid", type: "E", funds: "3000" }
    ],
    project = [];

grants.forEach(function (a) {
    !project.some(function (b, i) {
        if (a.id === b.id) {
            project[i].type.push(a.type);
            project[i].funds.push(a.funds);
            return true;
        }
    }) && project.push({ id: a.id, location: a.location, type: [a.type], funds: [a.funds] });
});
document.write('<pre>' + JSON.stringify(project, 0, 4) + '</pre>');
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Thank you for this too! This also creates an array from the ones with only one item in them, which might be useful in my project later. I also like that you got rid of the unnecessary jQuery! :P (I did upvote your post but I don't have enough rep to show my votes.) – tmrk Sep 26 '15 at 12:03
1

This will do:

var tempArr = [];
var result  = [];
for(i in grants){
  var rowObj = grants[i];
  var idPos  = tempArr.indexOf(rowObj.id);
  if(idPos > -1){
     result[idPos].type.push(rowObj.type);
     result[idPos].funds.push(rowObj.funds);
  }else{
    tempArr.push(rowObj.id);
    rowObj.type  = [rowObj.type]
    rowObj.funds = [rowObj.funds]
    result.push(rowObj);
  }
}
console.log(result);
Beroza Paul
  • 2,047
  • 1
  • 16
  • 16
  • Thank you! This works as well. The logic reminds me of @nina-scholz 's solution. I saved this too here http://jsfiddle.net/3wp1j9rs/ – tmrk Sep 26 '15 at 12:23