1

Hello I have data given in that kind of form:

 const mockData = [
    {
      id: 1,
      title: 'Lorem ipsum dolor',
      assignee: 'Mr First',
      teams: 'team first',
      dueDate: 'Monday',
      status: 'CLOSE',
    },
    {
      id: 2,
      title: 'Sit amet',
      assignee: 'Mr second',
      teams: 'teams second',
      dueDate: '10/10/2022',
      status: 'OPEN',
    },
  ]

and I need to aggregate them to this kind of shape:

const aggregated = [
    {
      id: 1,
      title: 'title',
      items: [
        'Lorem ipsum dolor',
        'Sit amet',
      ],
    },
    {
      id: 2,
      title: 'assignee',
      items: [
        'Mr second',
        'Mr second',
      ],
    },
    {
      id: 3,
      title: 'teams',
      items: [
        'team first',
        'team second',
      ],
    },
    ...

  ]

But I'm not really sure how to achieve that. Here is my code however there is nothing really to show as it is totally wrong approach I assume because it generates plenty of repeated elements

   const output = mockData.reduce((acc: AgreggatedType, item: ItemType) => {
  Object.keys(item)
    .forEach((key) => {
      if (key) {
        acc.push({
          id: item.id,
          title: key,
          items: [],
        });
      }
    });
  return acc;
}, []);
Jacki
  • 632
  • 3
  • 14
  • 26
  • 1
    Wouldn't a better result be a Map, keyed on id, whose values look like `{ title: ['title1', 'title2', ...], assignee: ['assignee1', 'assignee2', ...], ...}`? – jarmod Apr 27 '23 at 16:09
  • I guess that kind of structure also would work – Jacki Apr 27 '23 at 16:13

2 Answers2

2

One way to collect property values for each object, identified by id is as follows:

const mockData = [{
    id: 1,
    title: "id1-t1",
    assignee: "id1-a1",
    teams: "id1-tm1",
  },
  {
    id: 1,
    title: "id1-t2",
    assignee: "id1-a2",
    teams: "id1-tm2",
  },
  {
    id: 2,
    title: "id2-t1",
    assignee: "id2-a1",
    teams: "id2-tm1",
  },
];

const map = mockData.reduce((acc, curr) => {
  if (!acc.has(curr.id)) {
    acc.set(curr.id, {});
  }

  const prev = acc.get(curr.id);

  for (const [key, value] of Object.entries(curr)) {
    prev[key] = (prev[key] || []).concat(value);
  }

  return acc;
}, new Map());

console.log("1:", map.get(1));
console.log("2:", map.get(2));

You can easily modify to strip out the id property values if you choose to.

jarmod
  • 71,565
  • 16
  • 115
  • 122
  • You solution seems to be aggregating by the id, while the OP's example is by property. If you look at the example result, the id's are unrelated to the input. – Darryl Noakes Apr 27 '23 at 19:55
  • @DarrylNoakes yes, it's organized differently (and more ideally, in my opinion). The change relates to the conversation in comments on the original post. That said, it does show a generally useful, very simple solution using reduce that the OP can customize as desired. – jarmod Apr 27 '23 at 19:58
  • Ah, yes, I see. I was thinking that IDs would be unique, and therefore keying by ID would basically give the original data. My thought was to key by property, and provide either just the original values, or the original values and the IDs of their source item. – Darryl Noakes Apr 27 '23 at 20:06
  • 1
    @DarrylNoakes that's certainly another viable approach. I think that the original requirement is somewhat ambiguous tbh, so hopefully the OP is reading and can clarify. – jarmod Apr 27 '23 at 20:12
2

N.B. This answer assumes that item IDs are unique, and that you are trying to aggregate by property.

Instead of having an array of result items, you could have a Map of them, keyed by the property name. So, for example, { status: ["CLOSE", "OPEN"] } instead of [{ title: "status", items: ["CLOSE", "OPEN"] }].

I'm not sure what the purpose of the ID in the resulting data was, so I have not included in case I misinterpret it. If it is important beyond simply being part of the data structure you used, please comment. If they were intended to somehow relate to the source item IDs, see the second snippet.

Here is a basic function that will produce the data structure I described:

function aggregate(data) {
  const aggregated = new Map()

  for (const item of data) {
    for (const [key, value] of Object.entries(item)) {
      if (!aggregated.has(key)) aggregated.set(key, [])
      aggregated.get(key).push(value)
    }
  }

  return aggregated
}

const mockData = [{
    id: 1,
    title: 'Lorem ipsum dolor',
    assignee: 'Mr First',
    teams: 'team first',
    dueDate: 'Monday',
    status: 'CLOSE',
  },
  {
    id: 2,
    title: 'Sit amet',
    assignee: 'Mr second',
    teams: 'teams second',
    dueDate: '10/10/2022',
    status: 'OPEN',
  },
]

// The Stack Snippet console can't do Maps.
console.log(Object.fromEntries(aggregate(mockData).entries()))

If you need to know the ID of the original item, you can modify it slightly to produce [id, value] pairs:

function aggregate(data) {
  const aggregated = new Map()

  for (const item of data) {
    for (const [key, value] of Object.entries(item)) {
      if (!aggregated.has(key)) aggregated.set(key, [])
      aggregated.get(key).push([item.id, value]) // This is the only change.
    }
  }

  return aggregated
}

const mockData = [{
    id: 1,
    title: 'Lorem ipsum dolor',
    assignee: 'Mr First',
    teams: 'team first',
    dueDate: 'Monday',
    status: 'CLOSE',
  },
  {
    id: 2,
    title: 'Sit amet',
    assignee: 'Mr second',
    teams: 'teams second',
    dueDate: '10/10/2022',
    status: 'OPEN',
  },
]

// The Stack Snippet console can't do Maps.
console.log(Object.fromEntries(aggregate(mockData).entries()))
Darryl Noakes
  • 2,207
  • 1
  • 9
  • 27
  • 1
    Wonder why the 'Run code snippet' feature [can't console.log Map objects](https://stackoverflow.com/questions/45571101/how-can-i-display-a-javascript-es6-map-object-to-console). I noticed that too. – jarmod Apr 27 '23 at 20:34