I have problem with javascript object(array) deep copy. I read many good way to deal with it. And I also know that jQuery has $.extend API to this problem. But my question is: Can I just using JSON stringify and parse method to solve this problem?
Here's my code:
function deepCopy(oldValue) {
var newValue
strValue = JSON.stringify(oldValue)
return newValue = JSON.parse(strValue)
}
var a = {
b: 'b',
c: [1,2,4],
d: null
}
copy = deepCopy(a)
console.log(a === copy) // false
console.log(a.c === copy.c) // false
PS: I've known that if no all objects are serializable, but the only situation I know is that when the object contains a property which is function. Any other situation?
If your object is "small" and contains exclusively serializable properties, a simple deepCopy hack using JSON serialization should be OK. But, if your object is large, you could run into problems. And if it contains unserializable properties, those'll go missing:
var o = {
a: 1,
b: 2,
sum: function() { return a + b; }
};
var o2 = JSON.parse(JSON.stringify(o));
console.log(o2);
Yields:
Object {a: 1, b: 2}
Interestingly enough, a fair number of deep-copy solutions in C# are similar serialization/deserialization tricks.
Addendum: Not sure what you're hoping for in terms of comparing the objects after the copy. But, for complex objects, you generally need to write your own Compare() and/or Equals() method for an accurate comparison.
Also notable, this sort of copy doesn't preserve type information.
JSON.parse(JSON.stringify(new A())) instanceof A === false
You can do it that way, but it's problematic for some of the reasons listed above:
I question the performance.
Do you have any non-serializable properties?
And the biggest: your clone is missing type information. Depending upon what you're doing, that could be significant. Did the implementor add methods to the prototype of your original objects? Those are gone. I'm not sure what else you'll lose.
I think what you looking for is something like this:
If you have a really nested object structure then to make a deep copy you can make use of JSON.stringify().
Please see below example:
var obj= {
'a':1,
'b':2,
'c': {
'd':{
'e' : 3
}
}
}
var newObj = {...obj};
var lastObj = JSON.parse(JSON.stringify(obj));
obj.c.d.e =19;
console.log('obj '+obj.c.d.e);
console.log('newObj '+obj.c.d.e);
console.log('lastObj'+lastObj.c.d.e);
Now lastObj is truly detached from obj while if you use ...(spread) operator than also it does not work in really complex objects.
Hope this helps!!
Related
While talking about the advantages of Map over plain old JS objects, MDN says:
An Object has a prototype, so there are default keys in the map that could collide with your keys if you're not careful. As of ES5 this can be bypassed by using Object.create(null), but this is seldom done.
This is a thing people typically warn against in the Internet. (Another often recommended solution is to have a hasOwnProperty check each time the object's keys are iterated over).
I have a trouble with understanding this issue, given I cannot observe it.
See the below snippet - no spurious properties seem to manifest even though I did not create my 'map' via Object.create(null)! (Neither am I using an actual Map nor including hasOwnProperty checks.)
const dict = {a: 1, b: 2, c: 3}
document.body.innerHTML = `<pre><code></code></pre>`
for(const key in dict) {
const entry = `dict['${key}'] == ${dict[key]} // true\n`
document.querySelector('code').textContent += entry
}
When and how can the issue MDN docs are warning about manifest?
This happens when the prototype of the object has been modified, like a lot of libraries used to do in the past. You used to have code like: Array.prototype.merge = function() {};. Hence these days the advice is to never adjust native prototypes, but to subclass/extend the object first. For example class MyObj extends Object.
Object.prototype.serialize = function() {};
const dict = {a: 1, b: 2, c: 3}
document.body.innerHTML = `<pre><code></code></pre>`
for(const key in dict) {
const entry = `dict['${key}'] == ${dict[key]} // true\n`
document.querySelector('code').textContent += entry
}
In Safari with no add-ons (and actually most other browsers), console.log will show the object at the last state of execution, not at the state when console.log was called.
I have to clone the object just to output it via console.log to get the state of the object at that line.
Example:
var test = {a: true}
console.log(test); // {a: false}
test.a = false;
console.log(test); // {a: false}
I think you're looking for console.dir().
console.log() doesn't do what you want because it prints a reference to the object, and by the time you pop it open, it's changed. console.dir prints a directory of the properties in the object at the time you call it.
The JSON idea below is a good one; you could even go on to parse the JSON string and get a browsable object like what .dir() would give you:
console.log(JSON.parse(JSON.stringify(obj)));
What I usually do if I want to see it's state at the time it was logged is I just convert it to a JSON string.
console.log(JSON.stringify(a));
Vanilla JS:
#evan's answer seems best here. Just (ab)use JSON.parse/stringify to effectively make a copy of the object.
console.log(JSON.parse(JSON.stringify(test)));
JQuery specific solution:
You can create a snapshot of an object at a certain point in time with jQuery.extend
console.log($.extend({}, test));
What is actually happening here is jQuery is creating a new object with the test object's content, and logging that (so it will not change).
AngularJS (1) specific solution:
Angular provides a copy function that can be used to the same effect: angular.copy
console.log(angular.copy(test));
Vanilla JS wrapper function:
Here is an function which wraps console.log but will make a copy of any objects before logging them out.
I wrote this in response to a few similar but less robust functions in the answers. It supports multiple arguments, and will not try to copy things if they are not regular objects.
function consoleLogWithObjectCopy () {
var args = [].slice.call(arguments);
var argsWithObjectCopies = args.map(copyIfRegularObject)
return console.log.apply(console, argsWithObjectCopies)
}
function copyIfRegularObject (o) {
const isRegularObject = typeof o === 'object' && !(o instanceof RegExp)
return isRegularObject ? copyObject(o) : o
}
function copyObject (o) {
return JSON.parse(JSON.stringify(o))
}
example usage: consoleLogWithObjectCopy('obj', {foo: 'bar'}, 1, /abc/, {a: 1})
That > Object in the console, isn't only showing the current state. It actually is deferring reading the object and it's properties until you expand it.
For example,
var test = {a: true}
console.log(test);
setTimeout(function () {
test.a = false;
console.log(test);
}, 4000);
Then expand the first call, it will be correct, if you do it before the second console.log returns
using Xeon06's hint, you may parse his JSON in an object, and here is the log function I now use to dump my objects :
function odump(o){
console.log($.parseJSON(JSON.stringify(o)));
}
There is an option to use a debugger library.
https://debugjs.net/
Just include the script into your web page and put log statements.
<script src="debug.js"></script>
Logging
var test = {a: true}
log(test); // {a: true}
test.a = false;
log(test); // {a: false}
I defined an utility:
function MyLog(text) {
console.log(JSON.stringify(text));
}
and when I want to log on console I simply do:
MyLog("hello console!");
It works very well!
You might want to log the object in a human readable way:
console.log(JSON.stringify(myObject, null, 2));
This indents the object with 2 spaces at each level.
How can I pretty-print JSON using JavaScript?
There's a new option as of late 2022:
Deep copy the object with the new DOM structuredClone method:
console.log(structuredClone(obj))
which uses the same cloning algorithm that's used to transfer messages between web workers.
This should be faster and work with more types of objects than the JSON.parse(JSON.stringify(obj)) technique.
See https://developer.mozilla.org/en-US/docs/Web/API/structuredClone for details.
I may be shot for suggesting this, but this can be taken one step further. We can directly extend the console object itself to make it more clear.
console.logObject = function(o) {
(JSON.stringify(o));
}
I don't know if this will cause some type of library collision/nuclear meltdown/rip in the spacetime continuum. But it works beautifully in my qUnit tests. :)
Simply refresh the page after you open the console or open the console before you submit the request to the targeted page....
Just print whole object on console.
console.log(object);
Alright, so nothing fancy here, just some JSON:
var a = ["foo", "bar", "baz"];
var o = {a: "foo", b: "bar", c: "baz"};
But what happens when we do this?
typeof a; // => "object"
I'd like it if I could just get that command to say "array". But I can't. Is there any way to differentiate?
Use the instanceof operator.
if (a instanceof Array)
Keep in mind that all Arrays are Objects (as Object is on Array's prototype chain), so to distinguish between an Array and a not-Array you have to compare to the Array constructor, you can't use the Object constructor for this.
If you're not in a multi window environment, you can either check the constructor...
var isArray = possiblyAnArray.constructor == Array;
...or use instanceof...
var isArray = possiblyAnArray instanceof Array;
These won't work in a multi window environment, as the Array constructor of a different window will hold a different reference. In that case, you'd need to do it a slower way...
var isArray = ({}).toString.call(possiblyAnArray) == "[object Array]";
I've seen another method too, which is very easy to implement but not at all guaranteed to strictly tell you what's an Array or not.
Array.prototype.isArray = true;
var isArray = [].isArray;
I don't recommend using that, but it's interesting to see how it works.
You can check if the object is an instance of Array:
var isArray = a instanceof Array;
Another approach would to use duck typing. It's depends on the use case.
If, for instance, you want to count the items in the array, which is not something you can do in the JSON Object, you can try:
if (typeof(obj.length) !=="undefined"){ //do whatever}
This will give you the option to base your code on the objects attributes, rather than on it's real class, which might be less relevant in some cases.
Just found this: is there anything wrong with it?
var a = [4,5,6,7];
if(typeof a === object) a.type = "object";
if(a.__proto__ === Array.prototype) a.type = "array";
Since the ECMA-262 specifications Javascript has gained the Object.freeze() method, which allows for objects, whose properties can not be changed, added or removed.
var obj = {'a':1, 'b:2'};
Object.freeze(obj);
Object.isFrozen(obj); // returns true
obj.a = 10; // new assignment has no affect
obj.a; // returns 1
So far so good.
I am wondering, whether freeze() should also work on Arrays.
var arr = [1, 2];
Object.freeze(arr);
Object.isFrozen(arr); // returns true
arr[0] = 10;
arr; // returns [10, 2] ... ouch!
Maybe I am wrong, but I was under the impression, that Array inherits from Object.
typeof obj // "object"
typeof arr // "object"
Any ideas, pointers, enlightenments would be highly appreciated.
Yes, freeze should work for Arrays, the behavior you are experiencing is clearly an implementation bug.
This bug might be related to the fact that array objects implement a custom [[DefineOwnProperty]] internal method (the magic that makes the length property work).
I just tested it on two implementations and it works properly (Chrome 16.0.888, and Firefox Aurora 8.02a).
About your second question, well, array objects inherit from Array.prototype which inherits from Object.prototype, for example, you can access non shadowed methods from Object.prototype directly on array objects:
['a'].hasOwnProperty('0'); // true
But this isn't related about how the typeof works, this operator will return 'object' for any object intance, regardless its kind, and for the null value, which people has always complained about.
The rest of possible return values of the typeof operator, correspond to the primitive types of the language, Number, String, Boolean, Symbol and Undefined.
Yes, it is applicable to arrays too.
const arr = [1,2,3,4];
Object.freeze(arr);
Object.isFrozen(arr)// true
arr.push(5) // you will get a type error
arr.pop() // you will get a type error
Instead of freeze, use spread operator to copy things without modifying them (if you are using a transpiler, of course):
const second = {
...first,
test: 20
}
I have a simple object (or hash) in Javascript:
var settings = {
link: 'http://example.com',
photo: 'http://photos.com/me.jpg'
};
I need a copy of it. Is there a settings.clone() type method that will give me another object with the same attributes? I'm using jQuery, so happy to use a jQuery utility method if one exists.
Yes, extend an empty object with the original one; that way, everything will simply be copied:
var clone = $.extend({}, settings);
Extending some filled object with another, e.g.:
$.extend({a:1}, {b:2})
will return:
{a:1, b:2}
With the same logic:
$.extend({}, {foo:'bar', test:123})
will return:
{foo:'bar', test:123}
i.e. effectively a clone.
In a non jQuery way.
var newObj = {};
Object.keys(settings).forEach(function(key) {
newObj[ key ] = settings[ key ];
});
This copies only the top-level properties. To copy hashes with nested objects as property values, you will need to use a recursive function.
NB: The Object.keys(settings) avoids the need for calling settings.hasOwnProperty(key).
var clone = $.extend(true, {}, settings);
Set first argument to true.
EDIT: First argument true signifies deep copy. For given example in original question there is no need for deep copy since there are simple immutable key-value pairs. For question in title - as a general case - use deep copy. Otherwise you get half-copy.
It sounds like you want jQuery extend, which can copy an object for you.
http://api.jquery.com/jQuery.extend/
My 2 cents:
function clone(hash) {
var json = JSON.stringify(hash);
var object = JSON.parse(json);
return object;
}
It may not be the most optimized option but it can be handy for some scenarios.
Underscore.js also has an extend function if you are not using jQuery:
extend _.extend(destination, *sources) Copy all of the properties in the source objects over to the destination object, and
return the destination object. It's in-order, so the last source will
override properties of the same name in previous arguments.
_.extend({name: 'moe'}, {age: 50});
=> {name: 'moe', age: 50}