Home > front end >  How to merge array items according to a specific property value?
How to merge array items according to a specific property value?

Time:03-08

I'm trying to find the simplest way to merge this dynamic array using jQuery.

This is the result I'm getting after trying to use .reduce. The Month/Year object is dynamic, the result is based on the date range.

[
    {
        "ObjectGroupingId": 1,
        "Jan 2022": "Project1",
        "Feb 2022": 0,
        "Mar 2022": 0,
        "Apr 2022": 0,
        "May 2022": 0
    },
    {
        "ObjectGroupingId": 1,
        "Jan 2022": 0,
        "Feb 2022": "Project2",
        "Mar 2022": 0,
        "Apr 2022": 0,
        "May 2022": 0
    },
    {
        "ObjectGroupingId": 1,
        "Jan 2022": 0,
        "Feb 2022": 0,
        "Mar 2022": "Project3",
        "Apr 2022": 0,
        "May 2022": 0
    }
]

What I'm trying to achieve is this

[
    {
        "ObjectGroupingId": 1,
        "Jan 2022": "Project1",
        "Feb 2022": "Project2",
        "Mar 2022": "Project3"
    }
]

This is what I've tried in jQuery

var monthNames = data
        .map(function (t) {
            var monthName = formatMonthYear(t.dynamicHeaderColumn);
            return monthName;
        })
        .reduce(function (p, t) {
            if (p.indexOf(t) == -1)
                p.push(t);

            return p;
        }, []);

    // transform
    var result = data.reduce(function (p, t) {
        var monthName = formatMonthYear(t.dynamicHeaderColumn);

        var existing = p.filter(function (t2) {
            return t2.objectGroupingId == t.objectGroupingId;
        });

        if (existing.length) {
            existing[0][monthName] = t.projectName;
        } else {
            var n = {
                ObjectGroupingId: t.objectGroupingId
            };
            monthNames.forEach(function (m) {
                n[m] = 0;
            });

            n[monthName] = t.projectName;
            p.push(n);
        }

        return p;
    }, []);
    return result;

Is this possible? I'm not quite familiar in jQuery still learning it.

CodePudding user response:

Why does the OP want to use jQuery? jQuery mainly targets the domain of querying and manipulating the DOM. The OP's task in contrast deals with a single pure data structure and how to traverse/transform it.

Thus the OP should stick to the OP's original approach which uses methods of built-in objects like Array and Object.

The next provided code example uses two nested reduce tasks in order to achieve exactly the OP's desired result ...

function collectGroupedProjectData({ lookup, result }, { ObjectGroupingId, ...restData }) {
  let groupedData = lookup[ObjectGroupingId];

  if (!groupedData) {
    groupedData = lookup[ObjectGroupingId] = { ObjectGroupingId };

    result.push(groupedData);
  }
  Object
    .entries(restData)
    .reduce((group, [key, value]) => {

      if (value !== 0) {
        Object.assign(group, { [key]: value });
      }
      return group;

    }, groupedData);

  return { lookup, result };
}

const data = [{
  ObjectGroupingId: 1,
  'Jan 2022': 'Project1',
  'Feb 2022': 0,
  'Mar 2022': 0,
  'Apr 2022': 0,
  'May 2022': 0,
}, {
  ObjectGroupingId: 1,
  'Jan 2022': 0,
  'Feb 2022': 'Project2',
  'Mar 2022': 0,
  'Apr 2022': 0,
  'May 2022': 0,
}, {
  ObjectGroupingId: 1,
  'Jan 2022': 0,
  'Feb 2022': 0,
  'Mar 2022': 'Project3',
  'Apr 2022': 0,
  'May 2022': 0,
}, {
  ObjectGroupingId: 2,
  'Jan 2022': 'Project4',
  'Feb 2022': 0,
  'Mar 2022': 0,
  'Apr 2022': 0,
  'May 2022': 0,
}, {
  ObjectGroupingId: 2,
  'Jan 2022': 0,
  'Feb 2022': 'Project5',
  'Mar 2022': 0,
  'Apr 2022': 0,
  'May 2022': 0,
}, {
  ObjectGroupingId: 2,
  'Jan 2022': 0,
  'Feb 2022': 0,
  'Mar 2022': 'Project6',
  'Apr 2022': 0,
  'May 2022': 0,
}];

const listOfGroupedProjects = data
  .reduce(collectGroupedProjectData, { lookup: {}, result: [] }).result;

console.log({ listOfGroupedProjects });
.as-console-wrapper { min-height: 100%!important; top: 0; }

CodePudding user response:

Here is a simple way to achieve this task using a dictionary. I highly recommend you read up on how a dictionary works - they are very useful!

Just a quick explanation of the code, in function(e,d) the i is the index and d is the value. The second - function(key, value) is a little different. The key will return the value before the : instead of an index. This is because you are iterating over the values of an object ({ and } define the start and end of the object). value will return anything to the right of the :.

Update: I have updated the code to allow for any number of ObjectGroups as well as a pure JavaScript version.

var input = [
    {
        "ObjectGroupingId": 1,
        "Jan 2022": "Project1",
        "Feb 2022": 0,
        "Mar 2022": 0,
        "Apr 2022": 0,
        "May 2022": 0
    },
    {
        "ObjectGroupingId": 1,
        "Jan 2022": 0,
        "Feb 2022": "Project2",
        "Mar 2022": 0,
        "Apr 2022": 0,
        "May 2022": 0
    },
    {
        "ObjectGroupingId": 1,
        "Jan 2022": 0,
        "Feb 2022": 0,
        "Mar 2022": "Project3",
        "Apr 2022": 0,
        "May 2022": 0,
        "Jan 1900": 0
    },
    {
        "ObjectGroupingId": 2,
        "Jan 2022": 0,
        "Feb 2022": 0,
        "Mar 2022": 0,
        "Apr 2022": 0,
        "May 2022": 0,
        "Jan 1900": "Project10"
    }
]

var currentObjectGrouping = null;
var outputListJQuery = [];

$.each(input, function(i,d){
  $.each(d, function(key, value) {
    if(key === 'ObjectGroupingId') currentObjectGrouping = value;
    if(outputListJQuery[currentObjectGrouping] === undefined) outputListJQuery[currentObjectGrouping] = {};
    if(value !== 0) {
      outputListJQuery[currentObjectGrouping][key] = value;
    }
  });
});

outputListJQuery = outputListJQuery.filter(function( element ) {
   return element !== undefined;
})

console.log("This is using JQuery!");
console.log(outputListJQuery);

//Pure JavaScript

var outputListJavascript = [];

input.forEach(e => {
  for (const [key, value] of Object.entries(e)) {
    if(key === 'ObjectGroupingId') currentObjectGrouping = value;
    if(outputListJavascript[currentObjectGrouping] === undefined) outputListJavascript[currentObjectGrouping] = {};
    if(value !== 0) {
      outputListJavascript[currentObjectGrouping][key] = value;
    }
  }
});

outputListJavascript = outputListJavascript.filter(function( element ) {
   return element !== undefined;
})

console.log("This is using only JavaScript!");
console.log(outputListJavascript);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

CodePudding user response:

In a case where you have multiple ObjectGroupingIds the final result can be an object of objects where the high-level keys are the ObjectGroupingId values and the values the aggregated elements.

const data = [{"ObjectGroupingId": 1,"Jan 2022": "Project1","Feb 2022": 0,"Mar 2022": 0,"Apr 2022": 0,"May 2022": 0},{"ObjectGroupingId": 1,"Jan 2022": 0,"Feb 2022": "Project2","Mar 2022": 0,"Apr 2022": 0,"May 2022": 0},{"ObjectGroupingId": 1,"Jan 2022": 0,"Feb 2022": 0,"Mar 2022": "Project3","Apr 2022": 0,"May 2022": 0},{"ObjectGroupingId": 2,"Jan 2022": "Project4","Feb 2022": 0,"Mar 2022": 0,"Apr 2022": 0,"May 2022": 0},{"ObjectGroupingId": 2,"Jan 2022": 0,"Feb 2022": "Project5","Mar 2022": 0,"Apr 2022": 0,"May 2022": 0},{"ObjectGroupingId": 2,"Jan 2022": 0,"Feb 2022": 0,"Mar 2022": "Project6","Apr 2022": 0,"May 2022": 0}];

const aggData = data.reduce((acc, {ObjectGroupingId:id,...rest}) =>
    ({...acc,[id]:{...acc[id], ...Object.fromEntries(Object.entries(rest).filter(([k,v]) => v !== 0))}}), {}
);

console.log( aggData );

  • Related