How to deep copy a custom object in JavaScript? - javascript

I've been surfing around here a while and still haven't found an answer that worked for me.
Is there any way to deep copy a non-plain object in JS?
I've tried jQuery.extend(true, {}, this) but it only cloned some of it, the rest remained as a reference to another object.

Here are 3 different methods for copying objects. Each method has pros and cons, so read through and pick the best for your situation
Object.assign method
Use Object.assign, which "is used to copy the values of all enumerable own properties from one or more source objects to a target object". This copies both values and functions. At the time of writing this, browser support is good but not perfect, but this is the best method IMO of the three.
const obj1 = {a:1, b:2};
const obj1Copy = Object.assign(obj1)
Spread operator method
Alternatively, you can use the spread operator to spread from one object into another. Keep in mind that this will copy the values of keys, but if you the value of a key is a memory address (an other nested object or an array) then it will only be a shallow copy.
const obj1 = {a: () => {}, b:2}
const obj1Copy = { ...obj1 }
JSON stringify/parse trick
If the object doesn't have any circular references or functions as values, you can use the json stringify trick:
let myCopy = JSON.parse(JSON.stringify(myObject));
No libraries required, and works very well for most objects.

You can use lodash's cloneDeep function - https://lodash.com/docs/4.16.4#cloneDeep
Example (from docs)
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

A quick method to clone objects deep with performance into consideration.
JSON.parse(JSON.stringify({"foo":"bar"}))
How about performance ? >> [ May be this is the best way to deep copy objects ]. I strongly recommend you to checkout this video from Google Chrome Developers community on Youtube explaining how this method works and performance benchmarks.
Note: Use the JSON.parse method if your objects don't have Dates, functions, undefined, Infinity, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays or other complex types.
Source : Read this SO answer
Quick tip - React.JS initial state tree can be loaded from localStorage using this solution.

If you are dealing with a class instance you could use something like this.
You wouldn't need to copy the functions as they are delegated to on the prototype.
// myObject constructor
function myObject(foo, bar){
this.foo = foo
this.bar = bar
}
// delegate the functions to a prototype
myObject.prototype.something = function(){
console.log('something')
}
function instanceCopy(obj) {
// copy the object by the constructor
const copy = new obj.constructor()
const keys = Object.keys(obj)
keys.forEach(key => {
copy[key] = obj[key]
})
return copy
}
const myObj = new myObject('foo', 'bar')
const copyObj = instanceCopy(myObj)
console.log('myObj', myObj)
console.log('copyObj', copyObj)
console.log('same ?', copyObj === myObj)
// can we still call the functions
copyObj.something()
<script src="https://codepen.io/synthet1c/pen/WrQapG.js"></script>

You could use the structuredClone method:
const cloned = structuredClone(object)
Anyway, structuredClone allows you to do also other things that you might be interested in.
Check the documentation for further details:
https://developer.mozilla.org/en-US/docs/Web/API/structuredClone

Lodash _.cloneDeep() method kills the application performance. So I have come up with basic JavaScript solution. I have added it to my GIT repo. My application performance is back to normal after using my solution.
https://github.com/manideeppabba1991/javascript_util_functions/blob/master/clone_Array_or_Object.js

Related

Cloning arrays of objects with Object.assign

I discovered a bug on a project I'm working on that can be replicated by this snippet:
const original = [ { value: 1 } ];
function test() {
const copy = Object.assign([], original);
copy.forEach(obj => obj.value = obj.value + 1);
}
console.log(original[0].value); // -> 1, expected 1
test();
console.log(original[0].value); // -> 2, expected 1
test();
console.log(original[0].value); // -> 3, expected 1
I do not understand why this is the case. In the MDN web docs, the following statements can be found in the deep copy warning section:
For deep cloning, we need to use alternatives, because Object.assign() copies property values.
If the source value is a reference to an object, it only copies the reference value.
How do these notes apply to arrays / in this case? Are array values somehow considered as properties?
Looking back now, the method was probably not intended to work with arrays, so I guess I reap what I sow... but I'd still like to understand what's going on here. The intent was to deep copy the array in order to mutate the objects inside while keeping the original intact.
Are array values somehow considered as properties?
Yes. In JavaScript, arrays are objects (which is why Object.assign works with them), and properties with a special class of names called array indexes (strings defining decimal numbers in standard form with numeric values < 232 - 1) represent the elements of the array. (Naturally, JavaScript engines optimize them into true arrays when they can, but they're defined as objects and performing object operations on them is fully supported.) I found this sufficiently surprising when getting deep into JavaScript that I wrote it up on my anemic old blog.
Given:
const obj = {a: 1};
const arr = [1];
these two operations are the same from a specification viewpoint:
console.log(obj["a"]);
console.log(arr["0"]); // Yes, in quotes
Of course, we don't normally write the quotes when accessing array elements by index, normally we'll just do arr[0], but in theory, the number is converted to a string and then the property is looked up by name — although, again, modern JavaScript engines optimize.
const obj = {a: 1};
const arr = [1];
console.log(obj["a"]);
console.log(arr["0"]); // Yes, in quotes
console.log(arr[0]);
If you need to clone an array and the objects in it, map + property spread is a useful way to do that, but note the objects are only cloned shallowly (which is often sufficient, but not always):
const result = original.map((value) => ({...value}));
For a full deep copy, see this question's answers.
Here we can use structuredClone for deep copy.

how to prevent spread operator from maintaining original reference?

I'm setting an array to a property like this:
originalFacilityList: [...maintenanceInfo.Facilities]
However, when I check originalFacilityList downstream, it reflects the updates that were made to the maintenanceInfo.Facilities array. I was thinking that the spread operator was used to break that reference. Am I doing something incorrectly in this example?
When you do this originalFacilityList: [...maintenanceInfo.Facilities] you are effectively cloning the array. More precisely, you are doing a shallow clone, as opposed to deep clone.
As a result, when you add or remove items from the new array, the changes do not reflect on the original array:
const arr = [1, 2, 3];
const shallowClone = [...arr];
shallowClone.push(4);
console.log(shallowClone, arr);
What you have probably noticed is that the objects contained in the array are actually referenced by the old and the new array:
const arr = [{ property: 'value' }];
const shallowClone = [...arr];
arr[0].newProperty = 'newValue';
console.log(shallowClone);
If you want to avoid that, then you need to deep clone the array. There is no native solution for this, except some solutions like JSON.parse(JSON.stringify( that are very hacky and only work with serializable objects (does not preserve functions, prototypal inheritance, etc...), so either implement it yourself, or use utility libraries like Lodash's cloneDeep.

Cloning any javascript object by copying all own properties

If I wanted to clone any javascript object (that's not null), I would think I could just copy all of its own properties (enumerable and non-enumerable) -- using Object.getOwnPropertyNames -- onto a new empty object.
But I've noticed that an example of a deep cloning function provided by Dojo toolkit (https://davidwalsh.name/javascript-clone) treats RegExp, Date, and Node objects as special cases, and lodash.cloneDeep also has a lot of logic that is a lot more complicated than simply copying properties, including having some special cases of its own and apparently not supporting all types of objects: (https://github.com/lodash/lodash/blob/master/.internal/baseClone.js).
Why is simply copying the object properties not sufficient? What else is there to a javascript object besides its properties that I don't know about?
EDIT: to be clear, I'm talking about deep cloning an object. Sorry for the confusion.
If the top level properties are all value objects like strings and numbers then just copying the top level properties is fine for a clone of an object. If there are any reference objects such as dates, arrays or other objects then all your are doing is copying a reference from one object to another. If you change the reference object on the clone you will mutate the original object.
Take a look at my clone function at https://stackblitz.com/edit/typescript-qmzgf7
If it is an array it clones every item in the array, if it is a date it creates a new date with the same time, if it is an object it clones every property else if just copies the property.
The cloned object can now be mutated without worrying about effects it might have on the original object.
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: (typeof obj === 'object') && obj
? Object.getOwnPropertyNames(obj).reduce((o, prop) => ({ ...o, [prop]: clone(obj[prop]) }), {})
: obj;
let original = { prop1: "Original", objProp: { prop1: "Original" } };
let swallowCopy = { ...original };
let clonedObj = clone(original);
clonedObj.prop1 = "Changed";
clonedObj.objProp.prop1 = "Changed";
console.log(`Original objects properties are '${original.prop1}' and '${original.objProp.prop1}'`);
swallowCopy.prop1 = "Changed";
swallowCopy.objProp.prop1 = "Changed";
console.log(`Original objects properties are '${original.prop1}' and '${original.objProp.prop1}'`);
Notice how modifying the property on the object property shallow copy causes the original to change as well.
The easiest way to clone an object in JS is by using the ... spread operator.
Let's say you have this object:
const object = { foo: 1, bar: 2 }
To clone it, you can simply declare:
const objectClone = {...object}.
This will create all the properties present in the original object onto the clone, as well as their values.
Now the problem is, if you have any object nested in there, the copies will be made by reference. Suppose the original object is this instead:
const student = { studentID: 1, tests: { test1: 90, test2: 95}}
If you create a copy of that object by using the spread operator(or Object.assign, spread is just syntactic sugar), the nested object will actually point to the object inside the original object! So repeating this:
const studentClone = {...student}
And now you edit a property of the nested object inside the clone:
studentClone.tests.test1 = 80
This will change the value in both clone, and original object, as the nested object is really just pointing to 1 object in memory.
Now what those utilities, like _.cloneDeep will do, is iterate through all inner objects in the object you're cloning, and repeat the process. You could technically do it yourself, but you wouldn't be able to do it on objects with many nested objects easily. Something like this:
const studentClone = {...studentClone, tests: {...studentClone.tests}}
This would create new objects, with no reference problems.
Hope this helped!
EDIT: Just adding, object spreading would only work properly for prototype objects, of course. Each instantiated objects,such as arrays, Date objects etc, would have their own way of cloning.
Arrays can be copied similarly, through [...array]. It does follow the same rules regarding to references. For dates, you can simply pass the original date object into the Date constructor again:
const clonedDate = new Date(date)
This is where the third-party utilities will come in handy, as they'll usually handle most use cases.
This answer does a good job of explaining two of the problems with cloning a normal JavaScript object: prototype properties and circular references. But to answer your question regarding certain built-in types, the TL;DR answer is that there are 'under the hood' properties that you have no programmatic access to.
Consider:
let foo = [1, 2];
let bar = {};
Object.assign(bar, foo);
Object.setPrototypeOf(bar, foo.constructor.prototype); // aka Array.prototype
bar[0]; // 1
bar instanceof Array; // true
bar.map(x => x + 1); // [] ????
Empty array? Why? Just to make sure we're not crazy
foo.map(x => x + 1); // [2, 3]
The reason why map (and the other array methods) fail to work is that an Array isn't simply an object: it has internal slot properties for the stuff you put in it that you don't get to see as the JavaScript programmer. As another example, every JavaScript object has an internal [[Class]] property that says what kind of object it is. Fortunately for us, there's a loophole in the spec that allows us indirect access to it: the good ol Object.prototype.toString.call hack. So let's see what that has to say about various stuff:
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(3); // [object Number]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(/\w/); // [object RegExp]
Object.prototype.toString.call(JSON); // [object JSON]
Object.prototype.toString.call(Math); // [object Math]
Let's see what it says about our foo and bar:
Object.prototype.toString.call(foo); // [object Array]
Object.prototype.toString.call(bar); // [object Object] Doh!
There's no way to 'convert' a random object to an Array... or a Date... or an HTMLElement... or a regex. Now, there are in fact ways to clone all of those things, but they require special logic: you can't just copy properties, or even set the prototype, because they have internal logic you can't access or directly replicate.
In normal everyday JavaScript programming we don't worry too much about this stuff, it's the kind of thing that's generally of interest to library authors (or language implementers). We everyday working stiffs just use a library to cover the edge cases and call it a day. But every once in a while the abstractions we use leak and the ugly bubbles through. This is however a great illustration of why you should probably use battle-tested libraries rather than trying to roll your own.
An object in javascript includes fields and functions together, and every field could be another object (Like Date type). If you copy a date field, it will be a reference type assignment.
Example:
var obj1 = { myField : new Date('2018/9/17') };
var obj2 = {};
obj2.myField = obj1.myField;
Now, if we change "obj2.myField" like this:
obj2.myField.setDate(obj2.myField.getDate() + 2);
console.log(obj1.myField); // Result =====> Wed Sep 19 2018 00:00:00 GMT+0430
As you see, obj1 and obj2 still are linked.
Correct way to copy a date field:
obj2.myField = new Date(obj1.myField.getTime());
Most native objects(like you have mentioned - I don't know for is the correct naming for them; maybe built-in?) are treated as "simple": it does not make sense to copy Date object property-by-property. In the same time they all are mutable in some way.
let a = {test: new Date(1)}; // test === Thu Jan 01 1970 00:00:00GMT
let copy_a = {test: a.test}; // looks like cloned
a.test.setDate(12); // let's mutate original date
console.log(copy_a.test); // Thu Jan 12 1970 00:00:00GMT ooops modified as well
So you either should handle that exceptions(special cases) explicitly or take a risk of side effects for some cases.

Object.assign does not copy correctly

I'm working with VueJS.
I have a Method that receives a Object as argument.
Then I clone this Object with Object.assign().
Component.vue
export default {
// ...
methods: {
// ...
activateEditMode (item) {
this.editItemIndex = this.travelItinerary.indexOf(item)
this.editItem = Object.assign({}, item)
// ...
}
}
}
The original Object at this.roteiroCompleto[0]:
But when I edit the clone Object this.itemEditado:
the original Object this.roteiroCompleto[0] changes too.
I tried to copy each key and value, copy only the Array with .slice(), .map(a=>a), and nothing works. The two objects keep binding.
When I console.log(this.itemEditado), I get this:
The strange thing is, in another Vue Component, I use the same strategy, and it works.
Object.assign only does a shallow copy of the keys and values, meaning if one of the values in the object is another object or an array, then it is the same reference as was on the original object.
var x = { a: 10, b: { c: 100 } };
var y = Object.assign({}, x);
y.a = 20;
console.log( x.a, y.a ); // prints 10 20
y.b.c = 200;
console.log( x.b.c, y.b.c ) // prints 200 200
To deep copy an object, you can using something like the cloneDeep function in lodash or take an uglier approach using built in functions with JSON.parse( JSON.stringify( obj ) ).
Note that the second option will only work with primitive types that are supported by JSON.
If the methods you used isn't working well with objects involving data types, try this
import * as _ from 'lodash';
Deep clone object
myObjCopy = _.cloneDeep(myObj);
Solution from MDN
Object.assign(this.editItem, JSON.parse(JSON.stringify(item)))
In 2022, to deep clone objects natively on JavaScript you can use structuredClone
The global structuredClone() method creates a deep clone of a given
value using the structured clone algorithm.
MDN structuredClone()
You don't have to use a library, unless you really need a deep copy (I did not need one). Just do this:
this.editItem = {...item};
The ... operator will decompose item into its keys and values, and since you're doing that in an object literal (the { }), it uses those as the keys and values of the new object.
Might be helpful to other people who, like me, don't need a deep copy. Object.assign just straight-up doesn't work, and this does.

Native way to merge objects in Javascript

Javascript's Object doesn't have any native merge operation. If you have two objects, say
{a:1, b:2}
{c:3, d:4}
And want to get
{a:1, b:2, c:3, d:4}
As far as I know, you have to iterate through the objects. That is to say that you decide on either a merge left or merge right strategy and then you do something like (simplified)
for (key in object2) {
object1[key] = object2[key];
}
This is fine. However, Javascript has the call and prototype feature. For instance, turning arguments into an Array can be done with
Array.prototype.slice.call(arguments)
This approach exploits existing native code, and so therefore is less susceptible to programmer folly and should run faster than a non-native implementation.
The question
Is there a trick to use this prototype/call pattern on perhaps the Attribute or Node traversal features of the DOM, or perhaps some of the generic String functions in order to do a native object merge?
The code would look something like this:
var merged = somethingrandom.obscuremethod.call(object1, object2)
And as a result, you'd get a native merge without a traversal.
A possible, sub-optimal solution
If you could use the constructor property of an Object and then coerce one object to have a constructor of another object and then run new over the composite object, you may get a merge for free. But I don't have a firm grasp of the full implications of the constructor feature in javascript to make this call.
Lemma
The same question holds true for Arrays. A common problem is to take, say 7 arrays of numbers, then try to find out the intersection of those arrays. That is to say, which numbers exist in all 7 arrays.
You could concat them together, then do a sort, and then do a traversal, surely. But it would be nice if there is a generic intersect tucked away somewhere that we can coerce an array to doing natively.
Any thoughts?
edit:
Getting half way there
For the array problem, you could do the following:
array.concat(a, b, c).sort().join(':') and then use some tricky RegExp capture and repeat patterns in order to traverse. RegExp implementations, if you don't know, run on a very simple stack-based virtual machine. When you initialize your regular expression that's really a program that gets compiled (RegExp.compile is a deprecated JS method). Then the native runs over the string in a blisteringly fast way. Perhaps you could exploit that for membership thresholds and get better performance...
It still doesn't go all the way though.
My answer to this will be disappointing, but still:
no
The reason for this is simple: Mr Resig's implementation of merge (or "extend" as it's called for objects) in jQuery is doing a loop, just like the one in your question. You can look at it here. And I dare say that if John Resig hasn't found a clever build-in way to do it, then the mere mortals of stackoverflow won't either :)
Using ES6 (ES2015) you can use Object.assign method:
var x = {a:1, b:2};
var y = {c:3, d:4};
var z = Object.assign({},x,y);
Using ES7 (ES2016, Chrome 60+ or Babel) you can use Object spread operator:
var x = {a:1, b:2};
var y = {c:3, d:4};
var z = {...x, ...y};
The million dollar question! I've tried doing this numerous ways, and the loop way described above always seemed the dirtiest. ES6's Object.setPrototypeOf() allows you to delegate a "property override" object to a "default properties" object, pretty much accomplishing what you're trying to do, but using Object.setPrototypeOf() has some serious implications, like disabling the browser's compiler optimizations for the whole script.
Also, in both the loop solution and the Object.setPrototypeOf() solution, you are left with a situation where the "property override" object can mutate the "default properties" object:
defaultObj = {
a: [1, 2]
}
...
overrideObj = {
b: 3
}
Object.setPrototypeOf(overrideObj, defaultObj);
console.log(overrideObj); // {a: [1, 2], b: 3}
// Great!
...
overrideObj.a.push(4);
console.log(defaultObj); // {a: [1, 2, 4]}
// Uh-oh.
You might think this is not a problem, but let's say you're using this object as configuration for a 3rd party lib. You are now handing the control of your default object and everything referenced in it to the 3rd party lib.
A better solution might be to use JSON.stringify and JSON.parse to copy and combine the objects. Here's a Gist with the example:
https://gist.github.com/spikesagal/6f7822466887f19b9c65
HTH
Not that I know of, no. Also, you'll want to write your merge method like this:
function mergeInto(o1, o2) {
if (o1 == null || o2 == null)
return o1;
for (var key in o2)
if (o2.hasOwnProperty(key))
o1[key] = o2[key];
return o1;
}
You can do the following using native JS 1.7, without the need of a framework. See example on fiddle (example only intended for simple objects - not complex nested objects)
var obj1 = {a: "a", b: "b"};
var obj2 = {c: "c", d: "d"};
// The magic: ugly but works perfectly
var value = (JSON.stringify(obj1).concat(JSON.stringify(obj2))).replace("}{", ",");
document.getElementById("lbl1").setAttribute("value", value);
// back to object
var obj3 = JSON.parse(value);
document.getElementById("lbl2").setAttribute("value", obj3.a + " " + obj3.b + " " + obj3.c + " " + obj3.d);
You can combine the spread operator (...) and the Object.assign approach to get a good solution for the case where you have a bunch of objects in an array and want to merge them all into one super object.
const obj1 = {a:1, b:2}
const obj2 = {c:3, d:4}
const objArr = [obj1, obj2]
const mergedObj = Object.assign({}, ...objArr)
> { a: 1, b: 2, c: 3, d: 4 }
No native ways in ECMA-Script, use:
function merge(o1,o2) {
if (typeof(o1)!=='object') o1={};
if (typeof(o2)!=='object') o2={};
for (var k in o2) {
if (o1[k]!==undefined)
alert ('Collision Error'); // TODO
else
o1[k]=o2[k];
}
return o1;
}
Below I've included a deep-merge function I wrote. It will not deep-merge Arrays, only Objects. It will take two objects, and return a third, new object.
var merge = function(o1, o2) {
var o_new = {};
for(p in o1) {
if(o1[p]) {
if(typeof o1[p] == 'object' && !(o1[p] instanceof Array) && o2.hasOwnProperty(p)) {
o_new[p] = merge(o1[p], o2[p]);
}
else {
o_new[p] = o1[p];
}
}
}
for(p in o2) {
if(typeof o2[p] == 'object' && !(o2[p] instanceof Array) && o1.hasOwnProperty(p)) {
o_new[p] = merge(o1[p], o2[p]);
}
else {
o_new[p] = o2[p];
}
}
return o_new;
}

Categories