1

consider this array of object:

const relatedSites = [
  {
    "SubdomainName": "client1",
    "ClientName": "Eastern Region",
    "ClientAlias": "eastern-region"
  },
  {
    "SubdomainName": "client1",
    "ClientName": "City of Knox",
    "ClientAlias": "knox"
  },
  {
    "SubdomainName": "client2",
    "ClientName": "Eastern Region",
    "ClientAlias": "eastern-region"
  },
  {
    "SubdomainName": "client2",
    "ClientName": "City of Knox",
    "ClientAlias": "knox"
  }
]

I want to group these by the "SubdomainName" property and return an array of newly made objects using reduce so I get that result:

[
  {
    "title": "client1",
    "links": [
      {
        "url": "https://client1.com/eastern-region",
        "displayText": "Eastern Region"
      },
      {
        "url": "https://client1.com/knox",
        "displayText": "City of Knox"
      }
    ]
  },
  {
    "title": "client2",
    "links": [
      {
        "url": "https://client2.com/eastern-region",
        "displayText": "Eastern Region"
      },
      {
        "url": "https://client2.com/knox",
        "displayText": "City of Knox"
      }
    ]
  }
]

So far this is my solution but I think it looks a bit clunky, is there a more efficient way of doing it? I'm not liking the way I create a new group if I don't find it by title, also pushing the group to the array based on whether its index is found doesn't feel neat. Is there a more elegant way of achieving the same result?

  const groupRelatedSites = (groups, item) => {
    const group = groups.find(gp => gp.title === item.SubdomainName) || { title: item.SubdomainName, links: [] };
    const index = groups.findIndex(group => group.title === item.SubdomainName);
    const link = { url: `https://${item.SubdomainName}.com/${item.ClientAlias}`, displayText: item.ClientName };
    group.links = [...group.links, link];

    if (index === -1) {
      groups.push(group);
    } else {
      groups[index] = group;
    }
    return groups;
  };

  const grouped = relatedSites.reduce(groupRelatedSites, []);
Fabrice
  • 465
  • 1
  • 5
  • 17
  • You're definitely not the first one with that question: [How much research effort is expected of Stack Overflow users?](https://meta.stackoverflow.com/questions/261592/how-much-research-effort-is-expected-of-stack-overflow-users) – Andreas Jun 17 '21 at 08:35
  • @Andreas most example I found on stack return an object not an array and without that manipulation inside the reduce function. – Fabrice Jun 17 '21 at 08:37
  • You might not find a perfect match, but adjusting one of the many answers is not that complicated (e.g. `Object.entries()`). [Most efficient method to groupby on an array of objects](https://stackoverflow.com/questions/14446511/) has answers that already return an array. Or this one: [How to group an array of objects by key](https://stackoverflow.com/questions/40774697/), ... – Andreas Jun 17 '21 at 08:42
  • yep exactly my point, makes me wonder if you only read the title of my question and didn't bother with the rest, but thanks. – Fabrice Jun 17 '21 at 08:46
  • SO is not a free code writing service. There are hundreds of answers that group an array of objects. Many of them already return an array. Your "adjustments" with the `title` is not a problem of the grouping part. – Andreas Jun 17 '21 at 08:47

3 Answers3

3

You should be able to do this in a few lines using reduce, we create a map using the Subdomain name as the key, then we'll use Object.values to turn the resulting object into an array.

For example:

const relatedSites = [ { "SubdomainName": "client1", "ClientName": "Eastern Region", "ClientAlias": "eastern-region" }, { "SubdomainName": "client1", "ClientName": "City of Knox", "ClientAlias": "knox" }, { "SubdomainName": "client2", "ClientName": "Eastern Region", "ClientAlias": "eastern-region" }, { "SubdomainName": "client2", "ClientName": "City of Knox", "ClientAlias": "knox" } ]; 

const result = Object.values(relatedSites.reduce((acc, el) => { 
    acc[el.SubdomainName] = acc[el.SubdomainName] || { title: el.SubdomainName, links: [] };
    acc[el.SubdomainName].links.push({ url: `https://${el.SubdomainName}.com/${el.ClientAlias}`, displayText: el.ClientName });
    return acc;
}, {}))

console.log(result)
Terry Lennox
  • 29,471
  • 5
  • 28
  • 40
  • On the first pass for each SubdomainName, the value acc[el.SubdomainName] will be undefined, so we'll assign a new object to it. On further passes, we'll simply be adding more links. I hope this explains things a little better! – Terry Lennox Jun 17 '21 at 08:55
  • 1
    No problem, good luck with your project!! – Terry Lennox Jun 17 '21 at 08:59
1

You were close but were making it a little more complicated than need be

const relatedSites = [{
    "SubdomainName": "client1",
    "ClientName": "Eastern Region",
    "ClientAlias": "eastern-region"
  },
  {
    "SubdomainName": "client1",
    "ClientName": "City of Knox",
    "ClientAlias": "knox"
  },
  {
    "SubdomainName": "client2",
    "ClientName": "Eastern Region",
    "ClientAlias": "eastern-region"
  },
  {
    "SubdomainName": "client2",
    "ClientName": "City of Knox",
    "ClientAlias": "knox"
  }
]

let data = relatedSites.reduce((b, a) => {
    let ind = b.findIndex(e => e.title === a.SubdomainName)
    let obj = {
      url: "https://" + a.SubdomainName + ".com/" + a.ClientAlias,
      displayText: a.ClientAlias
    };
    if (ind > -1) {
      b[ind].links.push(obj)
    } else {
      b.push({
        "title": a.SubdomainName,
        "links": [obj]
      })
    }
    return b;
  }, []);

console.log(data)
Kinglish
  • 23,358
  • 3
  • 22
  • 43
0

const relatedSites = [{"SubdomainName": "client1","ClientName": "Eastern Region","ClientAlias": "eastern-region"},{"SubdomainName": "client1","ClientName": "City of Knox","ClientAlias": "knox"},{"SubdomainName": "client2","ClientName": "Eastern Region","ClientAlias": "eastern-region"},{"SubdomainName": "client2","ClientName": "City of Knox","ClientAlias": "knox"}];

const out = relatedSites.reduce((a, b) => {
  const title = b.SubdomainName;
  let obj = a.find(x => x.title === title);
  if (!obj) a.push({
    title: title,
    links: [{ 
      url: `https://${title}.com/${b.ClientAlias}`,
      displayText: b.ClientName,
    }] });
  else obj.links.push({
    url: `https://${title}.com/${b.ClientAlias}`,
    displayText: b.ClientName,
  });
  return a;
}, []);

console.log(out);
LaytonGB
  • 1,384
  • 1
  • 6
  • 20