Home > Software engineering >  Object Push into Array within For Loop/For Each Loop is causing duplicates
Object Push into Array within For Loop/For Each Loop is causing duplicates

Time:01-02

I would like to give each object in my Array a property that corresponds to an ID. I've tried this every possible way it seems like, below is the latest.

When I console.log the output individually it all looks good. However, when I run the output.push() and then log output, there are duplicates.

I found the below post which seems very similiar but after applying the changes suggested, I am still facing the same issue--In that I am seeing repeating IDs in the final output. The IndexID property is not unique and it should be for each iteration.

React object property value being duplicated on .push inside loop

Simple example first, then actual code example:

   flattenedWorkouts.forEach((currAlias,index) =>{
       
        const obj ={}
        const newMemberAttributes = Object.assign(obj, { alias: currAlias });
        newMemberAttributes.alias.indexID= index
                     
            console.log("memberAttributes:", newMemberAttributes);
         
            output.push(newMemberAttributes.alias)
    
    })

Snippet of the FlattenedWorkout elements that are incorrect...there are 100 so not including all i've trimmed down the object:

  Array [
      Object {
        "Round": "AMRAP 1",
        "indexID": 6,
   
      },
  Object {
        "Round": "AMRAP 1",
        "indexID": 6,
   
      },
  Object {
        "Round": "AMRAP 1",
        "indexID": 6,
   
      },

What I would expect to see:

 Object {
        "Round": "AMRAP 1",
        "indexID": 6,
   
      },
  Object {
        "Round": "AMRAP 1",
        "indexID": 7,
   
      },
  Object {
        "Round": "AMRAP 1",
        "indexID": 8,
   
      },

Actual Issue. Below is the starting Object.

var WorkoutDetail = [
  //Dummy item to act as begin of carasoul
  {
    RoundID: 1,
    Round: 'AMRAP 1',
    NumberOfSets: 3,
    Activity: [{
        key: '1',
        title: 'AMRAP 1',
        description: "Banded curtsy lunge to side leg raise",
      },
      {
        key: '2',
        title: 'AMRAP 1',
        description: "Inchworm with push-up",
      },
      {
        key: '3',
        title: 'AMRAP 1',
        description: "Static lunge with single arm press",
      }
    ]
  },

  {
    RoundID: 2,
    Round: 'AMRAP 2',
    NumberOfSets: 2,
    Activity: [{
        key: '4',
        title: 'AMRAP 2',
        description: "Side plank to leg extension",
      },
      {
        key: '5',
        title: 'AMRAP 2',
        description: "Burpee to bench jump",
      },
      {
        key: '6',
        title: 'AMRAP 2',
        description: "Banded heel tap jump squat",
      }
    ]
  },
  {
    RoundID: 3,
    Round: 'AMRAP 3',
    NumberOfSets: 1,
    Activity: [{
        key: '7',
        title: 'AMRAP 3',
        description: "Weighted pike plank",
      },
      {
        key: '8',
        title: 'AMRAP 3',
        description: "Incline push up",
      },
      { //Dummy item to make as caboose
        key: '9',
        title: 'AMRAP 3',
        description: "Weighted pike plank",
      },
      //Dummy item to make as caboose in carasoul
    ]
  }
]

// Next a Method I am using to loop over this object and * flatten * it out:

var FlatWorkoutDetail = () => {
  var flattenedWorkouts = []
  var iterationNumber = 0

  //Each Round
  WorkoutDetail.forEach(x => {

    for (let i = 1; i <= x.NumberOfSets; i  ) {
    
      for (let a = 0; a < x.Activity.length; a  ) {
        var obj = x.Activity[a]
        obj.Round = x.Round
        obj.SetNumber = i
        flattenedWorkouts.push(obj)
        iterationNumber  ;
      }
    }
  })

  return flattenedWorkouts
}

// Simple forEach Solution:
var FlatWorkoutDetailOutput = (val) => {
  val.forEach((obj, i) => obj.indexID = i);
}

// Execute the functions:
const TestArray = FlatWorkoutDetail()
const TestArray2 = FlatWorkoutDetailOutput(TestArray)

console.log(TestArray);

Final output is incorrect. 6 is the starting index and 14 is repeated twice:

Array [
  Object {
    "Round": "AMRAP 1",
    "SetNumber": 3,
    "description": "Banded curtsy lunge to side leg raise",
    "image": null,
    "indexID": 6,
    "key": "1",
    "time": " 45 Seconds",
    "title": "AMRAP 1",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Test.mp4",
  },
  Object {
    "Round": "AMRAP 1",
    "SetNumber": 3,
    "description": "Inchworm with push-up",
    "image": null,
    "indexID": 7,
    "key": "2",
    "time": " 45 Seconds",
    "title": "AMRAP 1",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout3.mp4",
  },
  Object {
    "Round": "AMRAP 1",
    "SetNumber": 3,
    "description": "Static lunge with single arm press",
    "image": null,
    "indexID": 8,
    "key": "3",
    "time": " 45 Seconds",
    "title": "AMRAP 1",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 1",
    "SetNumber": 3,
    "description": "Banded curtsy lunge to side leg raise",
    "image": null,
    "indexID": 6,
    "key": "1",
    "time": " 45 Seconds",
    "title": "AMRAP 1",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Test.mp4",
  },
  Object {
    "Round": "AMRAP 1",
    "SetNumber": 3,
    "description": "Inchworm with push-up",
    "image": null,
    "indexID": 7,
    "key": "2",
    "time": " 45 Seconds",
    "title": "AMRAP 1",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout3.mp4",
  },
  Object {
    "Round": "AMRAP 1",
    "SetNumber": 3,
    "description": "Static lunge with single arm press",
    "image": null,
    "indexID": 8,
    "key": "3",
    "time": " 45 Seconds",
    "title": "AMRAP 1",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 1",
    "SetNumber": 3,
    "description": "Banded curtsy lunge to side leg raise",
    "image": null,
    "indexID": 6,
    "key": "1",
    "time": " 45 Seconds",
    "title": "AMRAP 1",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Test.mp4",
  },
  Object {
    "Round": "AMRAP 1",
    "SetNumber": 3,
    "description": "Inchworm with push-up",
    "image": null,
    "indexID": 7,
    "key": "2",
    "time": " 45 Seconds",
    "title": "AMRAP 1",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout3.mp4",
  },
  Object {
    "Round": "AMRAP 1",
    "SetNumber": 3,
    "description": "Static lunge with single arm press",
    "image": null,
    "indexID": 8,
    "key": "3",
    "time": " 45 Seconds",
    "title": "AMRAP 1",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 2",
    "SetNumber": 2,
    "description": "Side plank to leg extension",
    "image": null,
    "indexID": 12,
    "key": "4",
    "time": " 45 Seconds",
    "title": "AMRAP 2",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 2",
    "SetNumber": 2,
    "description": "Burpee to bench jump",
    "image": null,
    "indexID": 13,
    "key": "5",
    "time": " 45 Seconds",
    "title": "AMRAP 2",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 2",
    "SetNumber": 2,
    "description": "Banded heel tap jump squat",
    "image": null,
    "indexID": 14,
    "key": "6",
    "time": " 45 Seconds",
    "title": "AMRAP 2",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 2",
    "SetNumber": 2,
    "description": "Side plank to leg extension",
    "image": null,
    "indexID": 12,
    "key": "4",
    "time": " 45 Seconds",
    "title": "AMRAP 2",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 2",
    "SetNumber": 2,
    "description": "Burpee to bench jump",
    "image": null,
    "indexID": 13,
    "key": "5",
    "time": " 45 Seconds",
    "title": "AMRAP 2",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 2",
    "SetNumber": 2,
    "description": "Banded heel tap jump squat",
    "image": null,
    "indexID": 14,
    "key": "6",
    "time": " 45 Seconds",
    "title": "AMRAP 2",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 3",
    "SetNumber": 1,
    "description": "Weighted pike plank",
    "image": null,
    "indexID": 15,
    "key": "7",
    "time": " 45 Seconds",
    "title": "AMRAP 3",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 3",
    "SetNumber": 1,
    "description": "Incline push up",
    "image": null,
    "indexID": 16,
    "key": "8",
    "time": " 45 Seconds",
    "title": "AMRAP 3",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
  Object {
    "Round": "AMRAP 3",
    "SetNumber": 1,
    "description": "Weighted pike plank",
    "image": null,
    "indexID": 17,
    "key": "9",
    "time": " 45 Seconds",
    "title": "AMRAP 3",
    "video": "https://boolin-api2.herokuapp.com/fetchImage//Uploads/Game3899-Workout4.mp4",
  },
]

CodePudding user response:

I don't know what else is going on in your code, but you're mutating the initial object a lot. Mutation is the source of all evil, try to avoid it like COVID.

Do this, it should solve your index problem.

var FlatWorkoutDetailOutput = (val) =>{
                              // shallow copy of the object, it only works cause the object is flat
  return val.map((obj, i) => ({...obj, indexID: i}));

}

Now TestArray will have the same bug as before, but TestArray2 will have the correct indexID.

As a rule of thumb, every time you want an object to look differently than it originally was, create a new object based on the original one. If you have an array of objects, use map and return a new array with new objects based on the original objects.

So in your previous code instead of doing this

var obj = x.Activity[a]
obj.Round =  x.Round
obj.SetNumber = i
flattenedWorkouts.push(obj)
iterationNumber   ;

do this

var originalObj = x.Activity[a]
var newObj = { ...originalObj } // this is a shallow copy, if you want to copy deep nested objects look up some library
newObj.SetNumber = i
flattenedWorkouts.push(newObj)
iterationNumber   ;

CodePudding user response:

The duplicate index values in the result can't be explained by the posted code.

In the code:

flattenedWorkouts.forEach((currAlias,index) =>{

There's no sample for flattenedWorkouts, so I'll use:

[{name: 'A'},{name: 'B'},{name: 'C'}];

Then there is:

    const obj ={}
    const newMemberAttributes = Object.assign(obj, { alias: currAlias });

The result is that obj and newMemberAttributes reference the same object. It's the same as:

let obj = {alias: currAlias};
let newMemberAttributes = obj;

Object.assign does a shallow copy, so:

newMemberAttributes.alias === currAlias

i.e. the original object passed to forEach is assigned to newMemberAttributes.alias, its properties and values aren't copied. Any changes to the original object will be reflected in newMemberAttributes.alias.

Later there is:

        output.push({ ...obj, alias: currAlias })

output isn't defined anywhere so I've added let output = []. As obj references the same object as newMemberAttributes, it doesn't matter which one is used.

The destructuring of obj and merging with alias means that duplicate properties are overridden. obj only has two properties: alias and indexID, so its own alias property is overridden in the merge with currAlias. This has little practical effect as the values are identical anyway (i.e. they're both references to the currAlias object).

If all you want to do is add an indexID property to the objects in flattenedWorkouts, then just do:

flattenedWorkouts.forEach((obj, i) => obj.indexID = i);

If, on the other hand, you want to copy the objects first, then use map with Object.assign to copy them before adding indexID:

let output = flattenedWorkouts.map((obj, i) => Object.assign({}, obj, {indexID: i}));

Note that Object.assign does a shallow copy, so deeper objects are references, not copies.

The following code that might clear things up. Or not.

let flattenedWorkouts = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
let output = [];

flattenedWorkouts.forEach((currAlias, index) => {
  let obj = {};
  let newMemberAttributes = Object.assign(obj, {alias: currAlias});
  
  console.log('obj === newMemberAttributes: '   (obj === newMemberAttributes));
  console.log('obj.alias === currAlias    : '   (obj.alias === currAlias));
  
  newMemberAttributes.alias.indexID = index;
  
  console.log('iteration '   index   ' memberAttributes\n', newMemberAttributes);

  output.push({ ...obj, alias: currAlias});
})

console.log('output '   JSON.stringify(output));

If the intention is copy across the properties and values and add an indexID property, consider the following based on forEach (but also see map example below):

let arr = [{name: 'A'},{name: 'B'},{name: 'C'}];
let result = [];

arr.forEach((obj, i) => {
  let newObj = Object.assign({},obj, {indexID: i} );
  result.push(newObj);  
});

console.log(result);

// Check that original array objects aren't changed
console.log(
  'Compare values:\n'   
  JSON.stringify(arr[0])   '\n'  
  JSON.stringify(result[0])
);
// Check identity
console.log('arr[0] ===  result[0] ? '   (arr[0] ===  result[0]));

Example using map:

let arr = [{name: 'A'},{name: 'B'},{name: 'C'}];

let output = arr.map((obj, i) => ({...obj, indexID: i}));

console.log(output);

  • Related