Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested array group-by with lodash

Tags:

javascript

I have a small web-application which fetches data using API call, the response is array of reports, each report have unique id, application, type and title.

I want to map it into new object by grouping-by application and then by types as describe bellow.

Input:

[
  {
    "id": "REPORT1",
    "application": "APP1",
    "type": "TYPE1",
    "title": ""
  },
  {
    "id": "REPORT2",
    "application": "APP1",
    "type": "TYPE1",
    "title": ""
  },
  {
    "id": "REPORT3",
    "application": "APP1",
    "type": "TYPE2",
    "title": ""
  },
  {
    "id": "REPORT4",
    "application": "APP2",
    "type": "TYPE3",
    "title": ""
  }
]

Desired output:

{
  "APP1": {
    "TYPE1": [
      {
        "id": "REPORT1",
        "application": "APP1",
        "type": "TYPE1",
        "title": ""
      },
      {
        "id": "REPORT2",
        "application": "APP1",
        "type": "TYPE1",
        "title": ""
      }
    ],
    "TYPE2": [
      {
        "id": "REPORT3",
        "application": "APP1",
        "type": "TYPE2",
        "title": ""
      }
    ]
  },
  "APP2": {
    "TYPE3": [
      {
        "id": "REPORT4",
        "application": "APP2",
        "type": "TYPE3",
        "title": ""
      }
    ]
  }
}

With loadash I came up with this:

const output = _(input)
  .groupBy(report => report.application)
  .value()

After group by application, I need to make another nested grouping or mapping by type but got stuck.

like image 632
sharonooo Avatar asked Dec 26 '18 09:12

sharonooo


2 Answers

You could take a dynamic approach with the wanted keys for grouping.

groups = ["application", "type"]

Then reduce the data array and the keys array and build either new objects, if necessary or with the last group an array for pushing the actual object.

This solution can be easily extended to more nested groups, if necessary.

var data = [{ id: "REPORT1", application: "APP1", type: "TYPE1", title: "" }, { id: "REPORT2", application: "APP1", type: "TYPE1", title: "" }, { id: "REPORT3", application: "APP1", type: "TYPE2", title: "" }, { id: "REPORT4", application: "APP2", type: "TYPE3", title: "" }],
    groups = ["application", "type"],
    grouped = data.reduce((r, o) => {
        groups
            .reduce((group, key, i, { length }) =>
                group[o[key]] = group[o[key]] || (i + 1 === length ? [] : {}), r)
            .push(o);

        return r;
    }, {});
    
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
like image 40
Nina Scholz Avatar answered Sep 18 '22 13:09

Nina Scholz


This can be implemented quite easily without lodash via the Array#reduce() function.

For details on how this is achieved, see the comments in code snippets source code:

var input = [{
    "id": "REPORT1",
    "application": "APP1",
    "type": "TYPE1",
    "title": ""
  },
  {
    "id": "REPORT2",
    "application": "APP1",
    "type": "TYPE1",
    "title": ""
  },
  {
    "id": "REPORT3",
    "application": "APP1",
    "type": "TYPE2",
    "title": ""
  },
  {
    "id": "REPORT4",
    "application": "APP2",
    "type": "TYPE3",
    "title": ""
  }
];

var output = input.reduce((result, item) => {

  // Get app object corresponding to current item from result (or insert if not present)
  var app = result[item.application] = result[item.application] || {};

  // Get type array corresponding to current item from app object (or insert if not present)
  var type = app[item.type] = app[item.type] || [];

  // Add current item to current type array
  type.push(item);

  // Return the result object for this iteration
  return result;

}, {});

console.log(output);
.as-console-wrapper {
height:100% !important;
max-height:unset !important;
}
like image 83
Dacre Denny Avatar answered Sep 21 '22 13:09

Dacre Denny