1

I'm having issues creating a copy of an object array. I can't get the new reference to point to a new independent array.

function OBJ1(name, tags) {
    this.myname = name;
    this.mytags = tags;
    this.myvalue = 0;
}

function OBJ2(arg1) {
    this.arg1 = arg1;
    this.myarray = [];
}

var OBJ1_array = [];
var result_array2 = null;
var result;
OBJ1_array = createarray1();
for (i = 0; i < 2; i++) {
    result = createarray2();
}

function createarray1() {
    var myarray = [];
    myarray.push(new OBJ1("NAME", [1, 2, 3]));
    myarray.push(new OBJ1("others", [1, 2, 3]));
    myarray.push(new OBJ1("total", [1, 2, 3]));
    return myarray;
}

function createarray2() {
    var newarray = $.extend(true, [], OBJ1_array); // newarray should refer to a new array, not the same one as OBJ1_array
    OBJ1_array[0].myname = "CHANGED";
    console.log("categories", JSON.parse(JSON.stringify(OBJ1_array)));
    console.log("newarray", JSON.parse(JSON.stringify(newarray)));
}

Output:

testscript.js:45 categories (3) [{…}, {…}, {…}]0: {myname: "CHANGED", mytags: Array(3), myvalue: 0}1: {myname: "others", mytags: Array(3), myvalue: 0}2: {myname: "total", mytags: Array(3), myvalue: 0}length: 3__proto__: Array(0)
testscript.js:46 newArray (3) [{…}, {…}, {…}]0: {myname: "CHANGED", mytags: Array(3), myvalue: 0}1: {myname: "others", mytags: Array(3), myvalue: 0}2: {myname: "total", mytags: Array(3), myvalue: 0}length: 3__proto__: Array(0)

I expected OBJ1_array[0].myname="CHANGED"; to have no effect on the newly created array newArray. Things I've tried and didn't work:

var newArray = OBJ1_array.map(a => ({...a}));
var newarray=$.extend(true,[],OBJ1_array);

How can I solve this issue?

jo_va
  • 13,504
  • 3
  • 23
  • 47
H_squared
  • 1,251
  • 2
  • 15
  • 32
  • A good article with possible ways: https://medium.com/@Farzad_YZ/3-ways-to-clone-objects-in-javascript-f752d148054d – HarisH Sharma Mar 08 '19 at 10:00
  • Possible duplicate of [How do I correctly clone a JavaScript object?](https://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object) – cнŝdk Mar 08 '19 at 10:05

6 Answers6

3

The $.extend documentation says the following:

Undefined properties are not copied. However, properties inherited from the object's prototype will be copied over. Properties that are an object constructed via new MyCustomObject(args), or built-in JavaScript types such as Date or RegExp, are not re-constructed and will appear as plain Objects in the resulting object or array.

This means that the array with all plain object in it will be deeply merged/copied. However objects created with the new keyword will not be reconstructed. This leaves us with the following scenario:

The array copy works just fine, however since the elements in the array are created using the new keyword they are not further merged. When altering the array itself (pushing, popping, etc.) you can see that the array is indeed a copy.

The issue here is that you access one of the elements in the array and change the object (created with the new keyword). Both arrays still point to the same object, thus when reading from the other array which hold the same object reference you will also see this change.

demonstration image

To resolve this issue you have to also make a copy of each object in the array. Depending on your use-case you might be able to use Object.assign or Object.create have a look at the documentation before using them blindly.

I've also created a minimal example of the problem you face to give you some better understanding of the issue.

// setup
var array1, array2, array3, array4;
function Dummy(name) { this.name = name }


// test #1 - using plain objects
array1 = [{ name: 'Foo' }];
array2 = $.extend(true, [], array1);

array1[0].name = 'Bar';

console.log(array1[0].name, array2[0].name);


// test #2 - using the `new` keyword
array3 = [new Dummy('Foo')];
array4 = $.extend(true, [], array3);

array3[0].name = 'Bar';

console.log(array3[0].name, array4[0].name);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
  • Additional reading material: https://www.webreflection.co.uk/blog/2015/10/06/how-to-copy-objects-in-javascript – 3limin4t0r Mar 08 '19 at 19:23
1

The problem is in your loop and OBJ1 function. first time the OBJ1_array is ok but when you come second time its valued already changed..

you can try this code

function OBJ1(name, tags) {
   return {myname:name, tags:tags}
    //this.myvalue = 0;
}

function OBJ2(arg1) {
    this.arg1 = arg1;
    this.myarray = [];
}

var OBJ1_array = [];
var result_array2 = null;
var result;
OBJ1_array = createarray1();

for (i = 0; i < 2; i++) {
let tempArr = $.extend(true, [], OBJ1_array);
    result = createarray2();
OBJ1_array = tempArr;
}

function createarray1() {
    let myarray = [];
   myarray.push(new OBJ1("NAME", [1, 2, 3]));
    myarray.push(new OBJ1("others", [1, 2, 3]));
    myarray.push(new OBJ1("total", [1, 2, 3]));
    return myarray;
}

function createarray2() {
    let newarray =$.extend(true, [], OBJ1_array);// newarray should refer to a new array, not the same one as OBJ1_array

    OBJ1_array[0].myname = "CHANGED";
    console.log("categories", JSON.parse(JSON.stringify(OBJ1_array)));
    console.log("newarray", JSON.parse(JSON.stringify(newarray)));
}
Zulqarnain Jalil
  • 1,679
  • 16
  • 26
0

Updated the answer. Easiest way to achieve what you want is to use JSON.stringify with JSON.parse to create a unlinked copy of array of objects.

const OBJ1 = (name, tags) => ({
  myname: name,
  mytags: tags,
  myvalue: 0,
})

function createarray1() {
  var myarray=[];
  myarray.push(OBJ1("NAME", [1,2,3]));
  myarray.push(OBJ1("others", [1,2,3]));
  myarray.push(OBJ1("total", [1,2,3]));
  return myarray;
}

const arr = createarray1()
// here you create a copy of array
const newArr = JSON.parse(JSON.stringify(arr))
// apply changes directly to the copy
newArr[0].myname = 'Something else'
console.log(newArr)
console.log(arr)
dporechny
  • 638
  • 5
  • 12
0

Arrays and Objects are reference types, which means that when you make a copy by assignment, you are simply copying the reference and not the underlying array/object. In your case, when copying the array, you copy all of the object references, which will still point to the objects in your original array. You need to clone the objects too for it to work.

Use Array.map() to iterate over your array and copy each item.

Use Object.create() to make a shallow clone of each object. This function takes a prototype and property descriptors to create a new object. You can use Object.getPrototypeOf() Object.getOwnPropertyDescriptors() to pass it the prototype and property descriptors of your input object.

function OBJ1(name) {
  this.myname = name;
}

const array1 = [new OBJ1("NAME")];

const array2 = array1.map(obj =>
  Object.create(
    Object.getPrototypeOf(obj),
    Object.getOwnPropertyDescriptors(obj)
  )
);
array2[0].myname = 'Jack';

console.log(array1[0].myname);
console.log(array2[0].myname);
jo_va
  • 13,504
  • 3
  • 23
  • 47
  • const array2 = array1.map(Object.create); Here you are creating two properties by name "myname" in object and its prototype. – RK_15 Mar 08 '19 at 10:12
  • this creates array2, but does not make a copy. Add `this.tags=tags` in your constructor, and you will see that the array in array2 is empty. – H_squared Mar 08 '19 at 10:14
  • @RK_15, could you elaborate please, I do not understand why this is not copying the array and the inner object, I am mapping on array1 and piping each item through Object.create, which creates a new object with the same prototype/properties as the original object – jo_va Mar 08 '19 at 10:17
  • @H_squared, I do not understand, could you elaborate please ? – jo_va Mar 08 '19 at 10:18
  • @jo_va first the resultant object is not an array it is a plain object with numeric indexes second "Object.create" methods first argument is the new prototype for resultant object i.e. all the object properties will shift to its prototype. Just log the array2 before changing "myname" property and you will not see that property in the object because it is in prototype of that object. – RK_15 Mar 08 '19 at 10:20
  • @RK_15, applying array1.map() gives an array, not an object – jo_va Mar 08 '19 at 10:21
  • @jo_va ok first is right but what about second problem – RK_15 Mar 08 '19 at 10:22
  • @RK_15, about the second problem, both objects are independent, so there is no second problem, Object.create takes an object as argument and creates an object with the same prototype and properties – jo_va Mar 08 '19 at 10:23
  • @jo_va No it takes first argument as the prototype of the new object and second argument as the complete property descriptor of new properties. Yes it will not create any problem while using the object properties directly But when you serialize the object using JSON.stringify() then that property will not be serialized. – RK_15 Mar 08 '19 at 10:26
  • @RK_15, thank you for the link, I understand what you mean now. I will update the answer accordingly – jo_va Mar 08 '19 at 10:35
0

I think you need a deep cloning of your object. please use below function

function clone(src) {
  var ret=(src instanceof Array ? [] : {});
  for(var key in src)
  {
    if(!src.hasOwnProperty(key)) { continue; }
    var val=src[key];
    if(val && typeof(val)=='object') { val=clone(val);  }
    ret[key]=val;
   }
 return ret;
}

function OBJ1(name, tags) {
    this.myname = name;
    this.mytags = tags;
    this.myvalue = 0;
}

function OBJ2(arg1) {
    this.arg1 = arg1;
    this.myarray = [];
}

var OBJ1_array = [];
var result_array2 = null;
var result;
OBJ1_array = createarray1();
for (i = 0; i < 2; i++) {
    result = createarray2();
}

function createarray1() {
    var myarray = [];
    myarray.push(new OBJ1("NAME", [1, 2, 3]));
    myarray.push(new OBJ1("others", [1, 2, 3]));
    myarray.push(new OBJ1("total", [1, 2, 3]));
    return myarray;
}

function createarray2() {
    var newarray = clone(OBJ1_array) ; // newarray should refer to a new array, not the same one as OBJ1_array
    OBJ1_array[0].myname = "CHANGED";
    console.log("categories", JSON.parse(JSON.stringify(OBJ1_array)));
    console.log("newarray", JSON.parse(JSON.stringify(newarray)));
}

Much Simpler Approach

var cloneOfOBJ1_array = JSON.parse(JSON.stringify(OBJ1_array));
Negi Rox
  • 3,828
  • 1
  • 11
  • 18
-1

solved cloning of an array of objects with Object.assign

const newArray = myArray.map(a => Object.assign({}, a));

or even shorter with spread syntax

const newArray = myArray.map(a => ({...a}));
  • Answer copy pasted from somewhere else. Plus I've tried this and it didn't workas mentioned in the original post. – H_squared Mar 08 '19 at 10:17