Skip to content

Instantly share code, notes, and snippets.

@holmberd
Last active November 28, 2024 16:16
Show Gist options
  • Save holmberd/945375f099cbb4139e37fef8055bc430 to your computer and use it in GitHub Desktop.
Save holmberd/945375f099cbb4139e37fef8055bc430 to your computer and use it in GitHub Desktop.
Dynamically create nested level groups by properties in Javascript

Dynamically create nested level groups by properties in Javascript

Code

/**
 * Creates nested groups by object properties.
 * `properties` array nest from highest(index = 0) to lowest level.
 *
 * @param {String[]} properties
 * @returns {Object}
 */
function nestGroupsBy(arr, properties) {
  properties = Array.from(properties);
  if (properties.length === 1) {
    return groupBy(arr, properties[0]);
  }
  const property = properties.shift();
  var grouped = groupBy(arr, property);
  for (let key in grouped) {
    grouped[key] = nestGroupsBy(grouped[key], Array.from(properties));
  }
  return grouped;
}

/**
 * Group objects by property.
 * `nestGroupsBy` helper method.
 *
 * @param {String} property
 * @param {Object[]} conversions
 * @returns {Object}
 */
function groupBy(conversions, property) {
  return conversions.reduce((acc, obj) => {
    let key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}

Example

var products = [
  { id: 1, name: 'p1', country: 'US', platform: 'win'},
  { id: 2, name: 'p1', country: 'US', platform: 'win'},
  { id: 3, name: 'p1', country: 'GB', platform: 'mac'},
  { id: 4, name: 'p1', country: 'US', platform: 'mac'},
  { id: 5, name: 'p2', country: 'US', platform: 'win'},
  { id: 6, name: 'p2', country: 'GB', platform: 'win'},
  { id: 7, name: 'p3', country: 'US', platform: 'mac'},
];

const groups = nestGroupsBy(products, ['name', 'country', 'platform']);
console.log(groups);
/* 
output:
{ p1: 
  { US: 
    { win: [
        {id: 1, name: 'p1', country: 'US', platform: 'win'},
        {id: 2, name: 'p1', country: 'US', platform: 'win'},          
      ], 
      mac: [
        {id: 4, name: 'p1', country: 'US', platform: 'mac'},
      ] 
     }, 
     GB: { win: [{ id: 3, name: 'p1', country: 'GB', platform: 'mac'}] } },
  p2: { US: { win: [Array] }, GB: { win: [Array] } },
  p3: { US: { mac: [Array] } } }  
*/
@Zignature
Copy link

Zignature commented Aug 6, 2024

It would be magnificent if you could add an optional callbacks array
e.g. const groups = nestGroupsBy(products, ['name', 'country', 'platform'], [callback1, callback2, callback3]);

For instance, I have a contacts list and I'd like to group them by the first letter of the last name of a contact.
I would need a callback function to get that first letter.

And then use something like this:

const groups = nestGroupsBy(contacts, ['lastname'], [getFirstLetter]);

const getFirstLetter = (prop) => {
     prop.substring(0, 1);
}

Obviously I'm not very good at Javascript, otherwise I would've done it myself 😄

@Zignature
Copy link

I updated the function using the new static function Object.groupBy().
When running on Node JS, version 21+ is required (compatibility list).

/**
 * Creates nested groups by object properties.
 * `properties` array nest from highest(index = 0) to lowest level.
 *
 * @param {String[]} properties
 * @returns {Object}
 */
function nestGroupsBy(arr, properties) {
  properties = Array.from(properties);
  if (properties.length === 1) {
    return Object.groupBy(arr, properties[0]);
  }
  const property = properties.shift();
  var grouped = Object.groupBy(arr, property);
  for (let key in grouped) {
    grouped[key] = nestGroupsBy(grouped[key], Array.from(properties));
  }
  return grouped;
}

const events = [
  {
    event_id: 57,
    title: "Saturday Night Fever",
    year: "2024",
    month: "07",
    day: "13"
  },
  {
    event_id: 58,
    title: "Let's Quiz! Music Quiz",
    year: "2024",
    month: "04",
    day: "03"
  },
  {
    event_id: 59,
    title: "Super Bowl LVIII",
    year: "2024",
    month: "02",
    day: "11"
  },
  {
    event_id: 62,
    title: "¡Tarde de Tapas!",
    year: "2023",
    month: "08",
    day: "15"
  }
];

const groupedEvents = nestGroupsBy(events, [({ year }) => year, ({ month }) => parseInt(month)]);
console.log(JSON.stringify(groupedEvents, null, '\t'));

Output:

{
	"2023": {
		"8": [
			{
				"event_id": 62,
				"title": "¡Tarde de Tapas!",
				"year": "2023",
				"month": "08",
				"day": "15"
			}
		]
	},
	"2024": {
		"2": [
			{
				"event_id": 59,
				"title": "Super Bowl LVIII",
				"year": "2024",
				"month": "02",
				"day": "11"
			}
		],
		"4": [
			{
				"event_id": 58,
				"title": "Let's Quiz! Music Quiz",
				"year": "2024",
				"month": "04",
				"day": "03"
			}
		],
		"7": [
			{
				"event_id": 57,
				"title": "Saturday Night Fever",
				"year": "2024",
				"month": "07",
				"day": "13"
			}
		]
	}
}

@moshfeu
Copy link

moshfeu commented Nov 28, 2024

Thanks @Zignature!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment