0

We have an array of objects with the attributes "description" and "id".

foo[0].id            // "45g-332"
foo[0].id2           // "45G-000332"
foo[0].description   // "tomatoes"
foo[1].id            // "45f-842"
foo[1].id2           // "45F-000842"
foo[1].description   // "cherries"
foo[2].id            // "45g-332"
foo[2].id2           // "45G-000332"
foo[2].description   // "peaches"

I need a variable or object in which all descriptions for the same id are combined to have a result like this:

bar[0].id            // "45g-332"
bar[0].id2           // "45G-000332"
bar[0].description   // "tomatoes; peaches"
bar[1].id            // "45f-842"
bar[1].id2           // "45F-000842"
bar[1].description   // "cherries"

… or with associative labels

bar["45g-332"].description   // "tomatoes; peaches"
bar["45f-842"].description   // "cherries"
bar["45g-332"].id2           // "45G-000332"

The only passably slim solution I came up with is (→ jsFiddle):

let foo = [];
foo[0]  = [];
foo[1]  = [];
foo[2]  = [];

foo[0].id            = "45g-332";
foo[0].id2           = "45G-000332";
foo[0].description   = "tomatoes";
foo[1].id            = "45f-842";
foo[1].id2           = "45F-000842";
foo[1].description   = "cherries";
foo[2].id            = "45g-332";
foo[2].id2           = "45G-000332";
foo[2].description   = "peaches";

let bar = [];
for (let i in foo) {      // Loop through source (foo)
  if (bar[foo[i].id]) {    // Check if sink (bar) with id already exists
    bar[foo[i].id].description += "; " + foo[i].description;  // Add description to existing bar element
  } else {
    bar[foo[i].id] = [];   // Create new bar element
    bar[foo[i].id].description = foo[i].description;
  };
  bar[foo[i].id].id2 = foo[i].id2;  // Added by edit
};

for (let i in bar) {
  console.log("id: " + i + " has: "  + bar[i].description + " and id2: " + bar[i].id2);
};

// Result is
// "id: 45g-332 has: tomatoes; peaches and id2: 45G-000332"
// "id: 45f-842 has: cherries and id2: 45F-000842"

I'm pretty sure that there's a more plain way to do this. What's gold standard?

Robbit
  • 128
  • 1
  • 11
  • 2
    Will [this answer](https://stackoverflow.com/questions/14446511/most-efficient-method-to-groupby-on-an-array-of-objects) work? (It will group the matching id's into an array instead of concatenating the descriptions.) https://jsfiddle.net/0ndvre1w/ – Ivar Nov 14 '22 at 13:23
  • @Ivar Yes, this uses the same compact method. Though currently I need the concatenated result. But this will help me understanding the mechanism of reduce(). Thank you. – Robbit Nov 14 '22 at 17:07

2 Answers2

2

You can use reduce

const foo = [{
    id: '45g-332',
    id2: '45G-000332',
    description: 'tomatoes'
  },
  {
    id: '45f-842',
    id2: '45F-000842',
    description: 'cherries'
  },
  {
    id: '45g-332',
    id2: '45G-000332',
    description: 'peaches'
  },
  {
    id: '45g-332',
    id2: '45G-000332',
    description: 'x'
  }
]

const bar = foo.reduce((acc, {
  id,
  id2,
  description
}) => {
  if (acc[id]) {
    acc[id].id2.push(id2)
    acc[id].description.push(description)
  } else {
    acc[id] = {
      id2: [id2],
      description: [description]
    }
  }

  return acc
}, {})

console.log(bar)
kennarddh
  • 2,186
  • 2
  • 6
  • 21
  • Very elegant, thank You! I didn't yet figure out how reduce works, so what would I have to do if I want to pass into bar another attribute that doesn't vary beside the same id, like an idBase64? – Robbit Nov 14 '22 at 17:02
  • Can you add example input? – kennarddh Nov 15 '22 at 03:51
  • I've edited my post, now it contains also an example for the additional question. Thanks in advance for considering. – Robbit Nov 15 '22 at 15:17
  • So you want to group by other id? – kennarddh Nov 15 '22 at 15:20
  • No, by the regular id. In the end it should be grouped by id (which is actually not needed anymore) and stored in a new array. Useful for me would also be a result like grouped by id with `bar[] = ` – Robbit Nov 15 '22 at 16:48
  • I've edited my answer. do you want it like that? – kennarddh Nov 16 '22 at 04:11
  • Thanks for Your intense support, but this way the code delivers the objects incomplete ("45f-842" only has the description) and the other attributes as a combined string instead of dinstinct attributes like `bar["45f-842"].description // is "cherries"`. My for-loop with console.log was only to show what I think the result should be like. :-) – Robbit Nov 19 '22 at 11:53
  • Can you add new expected output? – kennarddh Nov 19 '22 at 12:09
  • Sure. It should be like `bar["45g-332"].id2 → "45G-000332"`, `bar["45g-332"].description → "tomatoes"` and `bar["45f-842"].id2 → "45F-000842"`, `bar["45f-842"].description → "cherries"` and `bar["45g-332"].id2 → "45G-000332"`, `bar["45g-332"].description → "peaches"` – Robbit Nov 20 '22 at 22:04
  • 1
    I've edited my answer. You can't have two same property with different value in object – kennarddh Nov 21 '22 at 03:42
  • Yes that will do! :-) Thank You so much for Your insistently support. I'll try to get familiar now with `reduce()`. – Robbit Nov 22 '22 at 09:50
1

For your expected result 2 here is a simple method

    const foo = [
      {id: "45g-332", description: "tomatoes"},
      {id: "45f-842", description: "cherries"},
      {id: "45g-332", description: "peaches"}
    ]
    const bar = {}
    foo.forEach(d => {
       bar[d.id] = bar[d.id] ? bar[d.id] + "; " + d. description : d.description
    });

    console.log(bar)

For your expected result 1. you need to convert it back to an array

const foo = [
      {id: "45g-332", description: "tomatoes"},
      {id: "45f-842", description: "cherries"},
      {id: "45g-332", description: "peaches"}
    ]
    const bar = {}
    foo.forEach(d => {
       bar[d.id] = bar[d.id] ? bar[d.id] + "; " + d. description : d.description
    });

    const data = Object.keys(bar).map(key => ({id:key, description: bar[key]}))
    
    console.log(data)
sojin
  • 2,158
  • 1
  • 10
  • 18
  • Oh, thank You. This is the conservative way that is easy to comprehend and still pretty slim. :-) – Robbit Nov 14 '22 at 17:08