ang
ang

Reputation: 1581

How to denormalize array in JS

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?

Upvotes: 0

Views: 2071

Answers (2)

Akrion
Akrion

Reputation: 18525

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.

Upvotes: 1

Tyler Roper
Tyler Roper

Reputation: 21672

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.

Upvotes: 3

Related Questions