0

I have a data set of the following form

let data = [
  {
    "id": {
      "primary": "A1"
    },
    "msg": 1
  }, {
    "id": {
      "primary": "A1"
    },
    "msg": 2
  }, {
    "id": {
      "primary": "B2"
    },
    "msg": 3
  }
]

I would like to transform it to

newData = [
  {
    "id": {
      "primary": "A1"
    },
    "items": [
      { "msg": 1 },
      { "msg": 2 }
    ]
  },
  {
    "id": {
      "primary": "B2"
    },
    "items": [
      { "msg": 3 }
    ]
  }
]

I think the method is something like the following, but am not sure how to check against undefined values in this case.

let newData = [];
for (let i = 0; i < data.length; i++) {
  if (newData[i]['id']['primary'] === data[i]['id']) newData.push(data[i]['id'])
  else newData[i]['items'].push(data[i]['msg'])
}

How can I transform the original data set to merge entries with a matching primary id?

crashmstr
  • 28,043
  • 9
  • 61
  • 79
Matthew
  • 1,461
  • 3
  • 23
  • 49
  • 2
    JSON is JavaScript Object Notation. It is a string format to represent JavaScript objects. I see no `JSON.parse`, `JSON.stringify`, or anything else that indicates you are working with JSON. – crashmstr Jun 12 '19 at 19:23

2 Answers2

3

One option would be to use .reduce() to create a new array from the existing.

I've added comments to clarify.

let data = [ { "id": { "primary": "A1" }, "msg": 1 }, { "id": { "primary": "A1" }, "msg": 2 }, { "id": { "primary": "B2" }, "msg": 3 } ];

let result = data.reduce((out,item) => {
  let {id, ...items} = item;                      //Separate the "id" and "everything else"
  let existing = out.find(({id}) => id.primary == item.id.primary); 

  existing                                        //have we seen this ID already?
    ? existing.items.push(items)                  //yes - add the items to it
    : out.push({ id: {...id}, items: [items]});   //no - create it
    
  return out;
  }, []);
  
console.log(result);

A couple notes:

  • You may notice that I've set the ID using id: {...id}, despite the id already being an object. This is because using the existing id object would create a reference, whereas {...id} creates a shallow copy.

  • I haven't specified the msg property anywhere. Instead, any properties that aren't id will be added to the items list (example below).

        let data = [ { "id": { "primary": "A1" }, "msg": 1, "otherStuff": "Hello World!" }, { "id": { "primary": "A1" }, "msg": 2, "AnotherThing": true }, { "id": { "primary": "B2" }, "msg": 3, "someOtherProperty": false } ];
    
        let result = data.reduce((out,item) => {
          let {id, ...items} = item;
          let existing = out.find(({id}) => id.primary == item.id.primary); 
    
          existing
            ? existing.items.push(items)
            : out.push({ id: {...id}, items: [items]});
            
          return out;
          }, []);
          
        console.log(result);

    That said, if you start to nest objects (other than ID), they will likely be included as references; ...items is only a shallow copy.

    If such a case, consider something like JSON.parse(JSON.stringify(...)) for a deep copy. Be sure to read the link though; there are caveats.

Tyler Roper
  • 21,445
  • 6
  • 33
  • 56
  • This looks good. Thank you for the help!! My actual data structure is a bit more complicated, so it's going to take me a minute to wrap my head around this... – Matthew Jun 12 '19 at 20:09
  • @Matthew The overall gist is that your ID will be copied into the new object (as you showed in your question). Everything else will be copied over and lumped into the `items` portion for the corresponding ID. – Tyler Roper Jun 12 '19 at 20:12
  • Got it. Thank you!! :) – Matthew Jun 13 '19 at 01:37
1

You could also solve this in a concise way via the Array.reduce and ES6 destructuring:

let data = [ { "id": { "primary": "A1" }, "msg": 1 }, { "id": { "primary": "A1" }, "msg": 2 }, { "id": { "primary": "B2" }, "msg": 3 } ]

let result = data.reduce((r, {id, msg}) => 
  ((r[id.primary] = r[id.primary] || { id, items: [] }).items.push({msg}), r), {})

console.log(Object.values(result))

In more readable format it is:

let data = [ { "id": { "primary": "A1" }, "msg": 1 }, { "id": { "primary": "A1" }, "msg": 2 }, { "id": { "primary": "B2" }, "msg": 3 } ]

let result = data.reduce((r, {id, msg}) => {
  r[id.primary] = (r[id.primary] || { id, items: [] })
  r[id.primary].items.push({msg})
  return r
}, {})

console.log(Object.values(result))

The idea is to group by the id.primary and then once the grouping is done simply get the values via Object.values

Notice that this is one pass solution where you do not have to per each iteration do an Array.find against the current accumulator.

Akrion
  • 18,117
  • 1
  • 34
  • 54
  • What does the `{id, msg}` syntax do in this context? – Matthew Jun 13 '19 at 01:26
  • This is just ES6 object destructuring. I added a link to my answer. – Akrion Jun 13 '19 at 01:28
  • Would `{id}, {msg}` be equivalent? – Matthew Jun 13 '19 at 01:31
  • It would be sure but it is not a common thing these days :) It just adds extra chars etc. The main idea here is that you are no longer doing a loop and inside of it another ... this is a more performant solution. – Akrion Jun 13 '19 at 01:34
  • Yes. Thank you Akrion!! – Matthew Jun 13 '19 at 01:38
  • How would you say, "this array index" instead of `msg`? i.e. if `msg` was an abstraction for several keys. – Matthew Jun 13 '19 at 02:05
  • The current array index is provided for you by the Array.reduce ... you can access it if you do `data.reduce((r, {id, msg}, i)` where the `i` would be the array index. – Akrion Jun 13 '19 at 02:07
  • Ah, I see it now. I was looking for `data.reduce((r, {id, msg}, i, arr)`. The `let item = arr[i]`. – Matthew Jun 13 '19 at 02:12
  • 1
    You're awesome. Thank you :) – Matthew Jun 13 '19 at 02:12
  • 1
    Worth noting that this is referential; changing a property or value in `data` will change `result` as well. Of course, this may not really be an issue depending on use-case. – Tyler Roper Jun 13 '19 at 03:27