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>

4 Answers 4

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>

Sign up to request clarification or add additional context in comments.

3 Comments

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: jsfiddle.net/kqtj315e
Glad I could be of assistance. I realized the mapping/joining could be simplified considerably (see edited answer for that)
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 stackoverflow.com/questions/32806786/… :) )
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];

1 Comment

Thank you so much, this is exactly what I wanted! Elegant and quick. (For future reference, I saved the full code in a fiddle: jsfiddle.net/3g444sy9 )
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);

1 Comment

Thank you! This works as well. The logic reminds me of @nina-scholz 's solution. I saved this too here jsfiddle.net/3wp1j9rs
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>');

1 Comment

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.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.