81

How can I copy every element of an array (where the elements are objects), into another array, so that they are totally independent?

I don't want changing an element in one array to affect the other.

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
hAlE
  • 1,283
  • 3
  • 13
  • 19
  • The array part is covered by the answers to [this question](https://stackoverflow.com/questions/7486085/copy-array-by-value), and the object part is covered by the answers to [this question](https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript). I've also fixed my answer below. *(You probably don't remember, but did you edit your question within the first five minutes to add the note about objects? Because several people, including me, answered it without seeming to have read that part.)* – T.J. Crowder Nov 16 '20 at 15:58
  • At least based on the title, the linked questions are about cloning, not copying values. `a = [1, 2, 3], b = [4, 5, 6]`. If you do `a = b.slice()` you have copied b. You have not copied the values of b into a. a's array may be referenced multiple places. – gman Jul 08 '22 at 05:09

8 Answers8

120

The key things here are

  1. The entries in the array are objects, and
  2. You don't want modifications to an object in one array to show up in the other array.

That means we need to not just copy the objects to a new array (or a target array), but also create copies of the objects.

If the destination array doesn't exist yet...

...use map to create a new array, and copy the objects as you go:

const newArray = sourceArray.map(obj => /*...create and return copy of `obj`...*/);

...where the copy operation is whatever way you prefer to copy objects, which varies tremendously project to project based on use case. That topic is covered in depth in the answers to this question. But for instance, if you only want to copy the objects but not any objects their properties refer to, you could use spread notation (ES2015+):

const newArray = sourceArray.map(obj => ({...obj}));

That does a shallow copy of each object (and of the array). Again, for deep copies, see the answers to the question linked above.

Here's an example using a naive form of deep copy that doesn't try to handle edge cases, see that linked question for edge cases:

function naiveDeepCopy(obj) {
    const newObj = {};
    for (const key of Object.getOwnPropertyNames(obj)) {
        const value = obj[key];
        if (value && typeof value === "object") {
            newObj[key] = {...value};
        } else {
            newObj[key] = value;
        }
    }
    return newObj;
}
const sourceArray = [
    {
        name: "joe",
        address: {
            line1: "1 Manor Road",
            line2: "Somewhere",
            city: "St Louis",
            state: "Missouri",
            country: "USA",
        },
    },
    {
        name: "mohammed",
        address: {
            line1: "1 Kings Road",
            city: "London",
            country: "UK",
        },
    },
    {
        name: "shu-yo",
    },
];
const newArray = sourceArray.map(naiveDeepCopy);
// Modify the first one and its sub-object
newArray[0].name = newArray[0].name.toLocaleUpperCase();
newArray[0].address.country = "United States of America";
console.log("Original:", sourceArray);
console.log("Copy:", newArray);
.as-console-wrapper {
    max-height: 100% !important;
}

If the destination array exists...

...and you want to append the contents of the source array to it, you can use push and a loop:

for (const obj of sourceArray) {
    destinationArray.push(copy(obj));
}

Sometimes people really want a "one liner," even if there's no particular reason for it. If you refer that, you could create a new array and then use spread notation to expand it into a single push call:

destinationArray.push(...sourceArray.map(obj => copy(obj)));
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Well this doesn't actually work on an example I am dealing with :( – hAlE Apr 26 '13 at 21:07
  • 1
    @user1720860: The above definitely clones array entries, which is what the question asked. If you have a more complex thing than is addressed by the above, I suggest posting a new question including the code / array structure you're dealing with so that people can help you with it. – T.J. Crowder Apr 26 '13 at 21:47
  • This didnt work for me either. http://stackoverflow.com/questions/28482593/copying-an-array-of-objects-into-another-array-in-javascript-deep-copy – jsbisht Feb 12 '15 at 16:32
  • if sourceArray is null, "apply" fails in IE8 – v-tec Apr 30 '15 at 09:58
  • @v-tec: I'm mildly surprised if it doesn't in other browsers as well. – T.J. Crowder Apr 30 '15 at 11:33
  • Cleaner way to ```push```: ```Array.prototype.push.apply(destinationArray, sourceArray);``` – Veikko Karsikko Apr 20 '16 at 18:04
  • @vepasto: I don't see that as cleaner at all. There *is* a cleaner option, in ES2015, but it's not that. – T.J. Crowder Apr 20 '16 at 18:11
  • 1
    `destinationArray.push(...sourceArray);` worked for me perfectly. – Kishor Pawar Apr 03 '17 at 10:08
  • With me the objects in the arrays are still linked – Erik van den Hoorn Nov 16 '20 at 13:59
  • @ErikvandenHoorn - Indeed! It's weird, the history of the question shows that even the first version of it talks about objects, but I'm normally pretty tuned into those things. It's *possible* the question was edited within the first five minutes, but... As you say, the original answer didn't copy the objects, it just created a new array with the same objects in it. I've fixed it, thanks for pointing it out. – T.J. Crowder Nov 16 '20 at 15:53
34

Easy way to get this working is using:

var cloneArray = JSON.parse(JSON.stringify(originalArray));

I have issues with getting arr.concat() or arr.splice(0) to give a deep copy. Above snippet works perfectly.

jsbisht
  • 9,079
  • 7
  • 50
  • 55
  • 1
    [As mentioned in another comment](https://stackoverflow.com/questions/3978492/fastest-way-to-duplicate-an-array-in-javascript-slice-vs-for-loop#comment41154579_23481165), worth noting that both `undefined` and any function inside your array will get converted to `null` during the `JSON.stringify` process, though this is the simplest way to clone both the array itself and all its children so that any further changes don't affect the original, so is still very useful if the above caveats don't cause any issues for your use case. – fredrivett Jun 16 '21 at 10:42
  • This way can be like the last chance, because this function has a high impact on productivity – Yurii Space Oct 31 '21 at 22:19
16

A great way for cloning an array is with an array literal and the spread syntax. This is made possible by ES2015.

const objArray = [{name:'first'}, {name:'second'}, {name:'third'}, {name:'fourth'}];

const clonedArr = [...objArray];

console.log(clonedArr) // [Object, Object, Object, Object]

You can find this copy option in MDN's documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Copy_an_array

It is also an Airbnb's best practice. https://github.com/airbnb/javascript#es6-array-spreads

Note: The spread syntax in ES2015 goes one level deep while copying an array. Therefore, they are unsuitable for copying multidimensional arrays.

MauricioLeal
  • 6,221
  • 2
  • 21
  • 28
  • Note that the question is about an array of *objects* and talks about not wanting modifying an object in the new array to modify it in the old. This doesn't copy the objects, just the array, so doesn't do what the OP asked. (Neither did my answer originally. **Years** later [today] someone pointed out that part of the question. I can't imagine how I missed it, but lots of people did. Weird. (Side note: `...` isn't an operator.) – T.J. Crowder Nov 16 '20 at 15:56
  • Very interesting, the MDN docs used to call it spread operator. The URL that I shared still has it like that: `https://developer.mozilla.org/.../Operators/Spread_operator#Copy_an_array`. Anyhow, I updated the answer to say spread syntax. – MauricioLeal Nov 18 '20 at 03:27
  • 1
    Yeah, MDN is community-edited and early on people not closely involved with it thought it was an operator. (In fact, I was the one who fixed MDN. ;-) ) – T.J. Crowder Nov 18 '20 at 07:37
5
var clonedArray = array.concat();
Ilya
  • 29,135
  • 19
  • 110
  • 158
  • Note that the question is about an array of *objects* and talks about not wanting modifying an object in the new array to modify it in the old. This doesn't copy the objects, just the array, so doesn't do what the OP asked. (Neither did my answer originally. **Years** later [today] someone pointed out that part of the question. I can't imagine how I missed it, but lots of people did. Weird. – T.J. Crowder Nov 16 '20 at 15:55
3

If you want to keep reference:

Array.prototype.push.apply(destinationArray, sourceArray);

Veikko Karsikko
  • 3,671
  • 1
  • 20
  • 15
  • 2
    Does not work for arrays with a length beyond 65536 because that is the maximum hardcoded argument limit in the Chrome browser. – Jack G Jul 20 '18 at 22:52
  • 1
    Note that the question is about an array of *objects* and talks about not wanting modifying an object in the new array to modify it in the old. This doesn't copy the objects, just the array, so doesn't do what the OP asked. (Neither did my answer originally. **Years** later [today] someone pointed out that part of the question. I can't imagine how I missed it, but lots of people did. Weird. – T.J. Crowder Nov 16 '20 at 15:55
2

There are two important notes.

  1. Using array.concat() does not work using Angular 1.4.4 and jQuery 3.2.1 (this is my environment).
  2. The array.slice(0) is an object. So if you do something like newArray1 = oldArray.slice(0); newArray2 = oldArray.slice(0), the two new arrays will reference to just 1 array and changing one will affect the other.

Alternatively, using newArray1 = JSON.parse(JSON.stringify(old array)) will only copy the value, thus it creates a new array each time.

iled
  • 2,142
  • 3
  • 31
  • 43
  • 2
    That's not true. JavaScript `array.slice(0)` returns new object each time it's called and changing one returned instance doesn't change others. If you are using "frameworks" that change this behavior, it's just a bug / bad design. – wonder.mice Apr 19 '19 at 21:23
  • 1
    Not sure if I agree with @wonder.mice. If I run the following in my browser console then it does change the object in the original array: const first = [{test: 'test'}]; const second = first.slice(); second.map(x=>x.test='test2'); When I then check first the value of test will have changed to "test2". – Erik van den Hoorn Nov 16 '20 at 13:50
  • @ErikvandenHoorn Well, yes, array is copied, but it's a "shallow" copy, not "deep" copy - elements still reference same values. Add new item to `second`, `first ` will not have it. – wonder.mice Nov 17 '20 at 01:57
0

I suggest using concat() if you are using nodeJS. In all other cases, I have found that slice(0) works fine.

bean5
  • 306
  • 2
  • 9
  • Note that the question is about an array of *objects* and talks about not wanting modifying an object in the new array to modify it in the old. This doesn't copy the objects, just the array, so doesn't do what the OP asked. (Neither did my answer originally. **Years** later [today] someone pointed out that part of the question. I can't imagine how I missed it, but lots of people did. Weird. – T.J. Crowder Nov 16 '20 at 15:55
0

structuredClone represents a novel approach to perform deep cloning.

const objArray = [{name:'first'}, {name:'second'}, {name:'third'}, {name:'fourth'}];

// Clone it
const clonedArr = structuredClone(objArray);
console.log(clonedArr)
Penny Liu
  • 15,447
  • 5
  • 79
  • 98