11

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.

5 Answers 5

13

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;
}

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

6 Comments

Good job doing it without loadash :)
Thx for mentioning reduce ……
You're welcome @HossamMaher
hey ! Do you know how to type this function with typescript ? and type the output ? Thanks
@Gautier something like this might work? gist.github.com/dacre-denny/a86211ea59e54cd03474db0106578888
|
8

es6, object spreading and reduce makes it so simpler

const 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": ""
  }
]


const output = input.reduce((acc, item) => ({
  ...acc,
  [item.application]: {
    ...acc[item.application],
    [item.type]: [
      ...(acc[item.application] && acc[item.application][item.type] || []),
      item,
    ]
  }
}), {})

console.log(output)

Comments

6

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; }

Comments

4

I personally found this more readable when combining _.groupBy and _.mapValues in Lodash:

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: "" }]

const output = _.mapValues(_.groupBy(input, i => i.application),app => _.groupBy(app, i => i.type))

console.log(output)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

1 Comment

Is it still readable when you group by by 5 keys, what about 10 keys?
0

I've written a short method to do this, check below snippet.

const groupByDeep = (collection, keys) => {
  return _.chain(collection)
    .map(item => _.map(keys, key => _.get(item, key)))
    .reduce((result, paths, idx) => {
      const items = _.get(result, paths.join('.'), []);
      _.set(result, paths.join('.'), [...items, collection[idx]]);
      return result;
    }, {})
    .value();
};

const 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": ""
  }
];

console.log(groupByDeep(input, ['application', 'type']));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.

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.