Ensuring that two separate objects have a unique total set of keys - javascript

I have a JS framework and the user can inject values sourced from two different files. The injectable values are identified by the key of the returned object. The problem is that having two separate files means that the keys could clash.
If I have two JS objects like so:
let one = {
foo: null,
bar: null
}
let two = {
foo: null,
baz: null
}
Is there some way I could ensure the keys are unique across these two objects? I don't think there is a great way to do this, just looking for some possible ideas. Obviously, the two objects above "clash" because they both contain the key "foo".

A vanilla js solution:
if(!Object.keys(one).every(key => !(key in two))) alert("dupe");

You can merge the two array keys and see if there is a duplicate
const one = {
foo: null,
bar: null
}
const two = {
foo: null,
baz: null
}
const array = Object.keys(one).concat(Object.keys(two));
const hasDuplicate = array.filter((item, index) => {
return array.indexOf(item) !== -1 && index !== array.indexOf(item)
}).length !== 0;

Just check if any key is in the other set. If so, they are not unique. Using some MDN is desirable here as it will break early under a match condition.
function unique(obj1,obj2){
var o2Keys = Object.keys(obj2);
return !Object.keys(obj1).some(k => o2Keys.some(k2 => k == k2));
}
let o1 = { foo: null, bar: null };
let o2 = { foo: null, bar: null };
let o3 = { foobar: null };
let o4 = { bar: null };
console.log(unique(o1,o2)); //false
console.log(unique(o1,o3)); //true
console.log(unique(o1,o4)); //false

Related

JavaScript function for multi-level spread syntax

I'm working with a JavaScript object that gives the default values for a library I am working with
const defaults = {
alpha: alpha_val,
beta: {
a: {
i: beta_a_i_val,
ii: beta_a_ii_val,
},
c: beta_c_val,
},
gamma: gamma_val,
};
So far, I've been overriding the defaults in the following way
const override = {
...defaults,
beta: {
...defaults.beta,
a: {
...defaults.beta.a,
i: beta_a_i_newval,
},
},
gamma: gamma_newval,
};
While this works, the true defaults object is quite large and so overrides of many values becomes tedious and ugly.
I'm wondering if there is a way to write a function to do this sort of "deep spread" automatically?
So, creating the override object above, for example, would look something like this:
const newvals = {
beta: {
a: {
i: beta_a_i_newval,
},
},
gamma: gamma_newval,
};
const overrides = someFunction(defaults, newVals)
It seems like this would be a common requirement, but I've been having trouble finding a way to actually do this. I'm very new to JavaScript, so any advice is greatly appreciated!
Yes, you can write a function that takes in the defaults object and the newvals object, and returns a new object that combines the two. One way to do this would be to use a recursive function, where you loop through the keys in the newvals object and perform a "deep spread" similar to what you did before.
Here is an example of how you could write the someFunction function:
function someFunction(defaults, newvals) {
const result = {...defaults};
for (const key of Object.keys(newvals)) {
if (typeof newvals[key] === 'object' && newvals[key] !== null) {
result[key] = someFunction(defaults[key], newvals[key]);
} else {
result[key] = newvals[key];
}
}
return result;
}
This function works by creating a new object called result that is a shallow copy of the defaults object. It then loops through the keys in the newvals object and checks if the value is an object. If it is, it calls the someFunction function recursively to merge the two objects. If the value is not an object, it simply assigns the new value to the key in the result object.
You can then use the someFunction function like this:
const overrides = someFunction(defaults, newVals);
function someFunction(defaults, newvals) {
const result = {...defaults};
for (const key of Object.keys(newvals)) {
if (typeof newvals[key] === 'object' && newvals[key] !== null) {
result[key] = someFunction(defaults[key], newvals[key]);
} else {
result[key] = newvals[key];
}
}
return result;
}
const newvals = {
beta: {
a: {
i: "beta_a_i_newval",
},
},
gamma: "gamma_newval",
};
const defaults = {
alpha: "alpha_val",
beta: {
a: {
i: "beta_a_i_val",
ii: "beta_a_ii_val",
},
c: "beta_c_val",
},
gamma: "gamma_val",
};
override = someFunction(defaults,newvals);
console.log(override)
You can use the lodash _.merge function for that:
const myFunc(options_){
const options = {}
const defaultOptions = {}
_.merge(options, defaultOptions)
// ... the rest of your myFunc code
}
From the docs:
it recursively merges own and inherited enumerable string keyed
properties of source objects into the destination object. Source
properties that resolve to undefined are skipped if a destination
value exists. Array and plain object properties are merged
recursively. Other objects and value types are overridden by
assignment. Source objects are applied from left to right. Subsequent
sources overwrite property assignments of previous sources.
generally, _.merge is the way I merge defaults and user options in the arguments in every JavaScript module I build.
I almost never write a project without using this method.
except that I have made a wrapper for it, which returns a new object instead of mutating the original one, and I've used _.mergeWith to only merge POJOs and not arrays. because this merges the arrays and not only the objects.
You can do the same if you wanted to.
What you are looking for is a recursive function that iterates an object and calls itself for each child object:
const defaults = {
alpha: "alpha_val",
beta: {
a: {
i: "beta_a_i_val",
ii: "beta_a_ii_val",
iii: "blah",
},
c: "beta_c_val",
},
d: null,
gamma: "gamma_val",
};
const newvals = {
beta: {
a: {
i: "beta_a_i_newval",
iii: null,
},
},
d: "blah",
gamma: "gamma_newval",
};
const result = cloneObj(defaults, newvals);
//make sure none of the original objects are affected
result.beta.a.test = "ok";
console.log("defaults", defaults);
console.log("newvals", newvals);
console.log("result", result);
function cloneObj(def, obj)
{
const ret = {};
//iterate through the default object
for(let key in def)
{
// get new value
let val = obj[key];
if (val === undefined)
val = def[key]; //fallback to default value
//if it's an object and not a NULL call itself
if (val && val instanceof Object)
val = cloneObj(def[key], val);
ret[key] = val;
}
return ret;
}

Changing a specific property inside an object changes that same property in another object of the same type Typescript

I have a really weird problem, let me just explain:
Obj1: DetailsType = {property1: 123, property2: [{subProp1: 'a', subProp2: 'b'}]}
Obj2: DetailsType = new DetailsType(Obj1)
This is the constructor of DetailsType:
constructor(value: DetailsType = <DetailsType>{}){
this.property1 = (value.property1 !== undefined) ? value.property1 : null
this.property2 = (value.property2 !== undefined) ? value.property2 : []
}
Now I run the following code
this.Obj2.property1 = 987
this.Obj2.property2[0].subProp1 = 'z'
At this point, for some reason, the value of Obj1.property2[0].subProp1 is 'z' Even though we changed the value of subProp1 for Obj2! However, Obj1.property1 is still 123
So why does changing property2 which is an array, affect the value on both objects?? How can property1, a number, work correctly, but property2 work so weirdly? It works vice versa, whether I change subProp1 for Obj1 or Obj2. I'm so confused.
Thanks for your help!
It is happening because the value.property2 is an object with many nested references to other objects inside it. You need to deep clone the value.property2 in the constructor:
class DetailsType {
constructor(value) {
this.prop1 = (value.prop1 !== undefined) ? value.prop1 : null
this.prop2 = (value.prop2 !== undefined) ? JSON.parse(JSON.stringify(value.prop2)) : []
}
}
let obj1 = {
prop1: 123,
prop2: [{
subProp1: 'a',
subProp2: 'b'
}]
};
let obj2 = DetailsType = new DetailsType(obj1);
obj2.prop1 = 987
obj2.prop2[0].subProp1 = 'z'
document.body.innerHTML += `obj1:<pre>${JSON.stringify(obj1, undefined, 2)}</pre>`;
document.body.innerHTML += `obj2:<pre>${JSON.stringify(obj2, undefined, 2)}</pre>`;
To find out various ways of deep cloning objects refer:
What is the most efficient way to deep clone an object in JavaScript?
Use library like Lodash _.cloneDeep(value)
The current answer (with JSON.stringify) is maybe not the worst, because it will work in this case. But we don't know, how your objects in the array of prop2 will actually look like. Because it is a TypeScript class, it could be, that you also add some methods to it. With JSON.stringify though, all methods/functions would be lost. Only the value-properties are retained, but also not in the right order. The result is just some anonymous object.
Because it is sometimes necessary to clone objects (and not have their properties referenced to the original object), it is pretty useful to add a .clone() function that can be used with every object and array.
This will work by just adding the following code:
(<any>Object.prototype).clone = function() {
const clone = (this instanceof Array) ? [] : {};
for (const propertyName in this) {
if (propertyName == 'clone') continue;
if (this[propertyName] && typeof this[propertyName] == "object") {
clone[propertyName] = this[propertyName].clone();
} else clone[propertyName] = this[propertyName]
} return clone;
};
With your given code, it will look like this (pretty clean):
class DetailsType {
public prop1;
public prop2;
constructor(value) {
this.prop1 = value.prop1 !== undefined ? value.prop1 : null;
this.prop2 = value.prop2 !== undefined ? value.prop2.clone() : [];
}
}
The .clone() method works both for arrays and objects and will create a deep copy.

lodash check object properties has values

I have object with several properties, says it's something like this
{ a: "", b: undefined }
in jsx is there any one line solution I can check whether that object's property is not empty or has value or not? If array there's a isEmpty method.
I tried this
const somethingKeyIsnotEmpty = Object.keys((props.something, key, val) => {
return val[key] !== '' || val[key] !== undefined
})
In lodash, you can use _.some
_.some(props.something, _.isEmpty)
You can use lodash _.every and check if _.values are _.isEmpty
const profile = {
name: 'John',
age: ''
};
const emptyProfile = _.values(profile).every(_.isEmpty);
console.log(emptyProfile); // returns false
Possible ways:
Iterate all the keys and check the value:
let obj = {a:0, b:2, c: undefined};
let isEmpty = false;
Object.keys(obj).forEach(key => {
if(obj[key] == undefined)
isEmpty = true;
})
console.log('isEmpty: ', isEmpty);
Use Array.prototype.some(), like this:
let obj = {a:0, b:1, c: undefined};
let isEmpty = Object.values(obj).some(el => el == undefined);
console.log('isEmpty: ', isEmpty);
Check the index of undefined and null:
let obj = {a:1, b:2, c: undefined};
let isEmpty = Object.values(obj).indexOf(undefined) >= 0;
console.log('isEmpty: ', isEmpty);
A simple and elegant solution to check if all property values in an object is empty or not is following,
const ageList = {
age1: 0,
age: null
};
const allAgesEmpty = _.values(ageList).every((age) => !age);
console.log(allAgesEmpty); // returns true
Regarding the function inside _.every
Note: Do not use _.empty inside every as empty of a number always return false.
Read more here: https://github.com/lodash/lodash/issues/496
Note: if a property has a boolean value then you may need to modify the function even more
const profile = {
name: 'John',
age: ''
};
const emptyProfile = .values(profile).every(.isEmpty);
This solution is more efficient to check if each key is really empty

Look through an array of objects and remove undefined from its property

I have an array of objects, I would like to remove 'undefined' from any of the properties in any of the objects.
To remove undefined from an object, I use this method,
removeNullorUndefined:function(model) {
function recursiveFix(o) {
// loop through each property in the provided value
for (var k in o) {
// make sure the value owns the key
if (o.hasOwnProperty(k)) {
if (o[k] === 'undefined') {
// if the value is undefined, set it to 'null'
o[k] = '';
} else if (typeof (o[k]) !== 'string' && o[k].length > 0) {
// if there are sub-keys, make a recursive call
recursiveFix(o[k]);
}
}
}
}
var cloned = $.extend(true, {}, model);
recursiveFix(cloned);
return cloned;
},
How can I modify this so it can also accept an array of objects and remove 'undefined' from it ?
Appreciate any inputs
As long as the value is undefined and not a string value of 'undefined' then one way is to use JSON.stringify. Referring to property values:
If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).
So, you could stringify an object and immediately parse it to remove undefined values.
NOTE: This approach will deep clone the entire object. In other words if references need to be maintained this approach won't work.
var obj = {
foo: undefined,
bar: ''
};
var cleanObj = JSON.parse(JSON.stringify(obj));
// For dispaly purposes only
document.write(JSON.stringify(cleanObj, null, 2));
An added bonus is without any special logic it will work at any depth:
var obj = {
foo: {
far: true,
boo: undefined
},
bar: ''
};
var cleanObj = JSON.parse(JSON.stringify(obj));
// For dispaly purposes only
document.write(JSON.stringify(cleanObj, null, 2));
If it is a string value of 'undefined' you can use the same approach but with a replacer function:
var obj = {
foo: {
far: true,
boo: 'undefined'
},
bar: ''
};
var cleanObj = JSON.parse(JSON.stringify(obj, replacer));
function replacer(key, value) {
if (typeof value === 'string' && value === 'undefined') {
return undefined;
}
return value;
}
// For dispaly purposes only
document.write(JSON.stringify(cleanObj, null, 2));
If you like the way removeNullorUndefined() currently works then you might try:
items.forEach(function(item){ removeNullorUndefined(item); });

How to Deep clone in javascript

How do you deep clone a JavaScript object?
I know there are various functions based on frameworks like JSON.parse(JSON.stringify(o)) and $.extend(true, {}, o) but I don't want to use a framework like that.
What is the most elegant or efficient way to create a deep clone.
We do care about edge cases like cloning array's. Not breaking prototype chains, dealing with self reference.
We don't care about supporting copying of DOM objects and like because .cloneNode exists for that reason.
As I mainly want to use deep clones in node.js using ES5 features of the V8 engine is acceptable.
[Edit]
Before anyone suggests let me mention there is a distinct difference between creating a copy by prototypically inheriting from the object and cloning it. The former makes a mess of the prototype chain.
[Further Edit]
After reading your answer I came to the annoying discovery that cloning entire objects is a very dangerous and difficult game. Take for example the following closure based object
var o = (function() {
var magic = 42;
var magicContainer = function() {
this.get = function() { return magic; };
this.set = function(i) { magic = i; };
}
return new magicContainer;
}());
var n = clone(o); // how to implement clone to support closures
Is there any way to write a clone function that clones the object, has the same state at time of cloning but cannot alter the state of o without writing a JS parser in JS.
There should be no real world need for such a function anymore. This is mere academic interest.
Very simple way, maybe too simple:
var cloned = JSON.parse(JSON.stringify(objectToClone));
It really depends what you would like to clone. Is this a truly JSON object or just any object in JavaScript? If you would like to do any clone, it might get you into some trouble. Which trouble? I will explain it below, but first, a code example which clones object literals, any primitives, arrays and DOM nodes.
function clone(item) {
if (!item) { return item; } // null, undefined values check
var types = [ Number, String, Boolean ],
result;
// normalizing primitives if someone did new String('aaa'), or new Number('444');
types.forEach(function(type) {
if (item instanceof type) {
result = type( item );
}
});
if (typeof result == "undefined") {
if (Object.prototype.toString.call( item ) === "[object Array]") {
result = [];
item.forEach(function(child, index, array) {
result[index] = clone( child );
});
} else if (typeof item == "object") {
// testing that this is DOM
if (item.nodeType && typeof item.cloneNode == "function") {
result = item.cloneNode( true );
} else if (!item.prototype) { // check that this is a literal
if (item instanceof Date) {
result = new Date(item);
} else {
// it is an object literal
result = {};
for (var i in item) {
result[i] = clone( item[i] );
}
}
} else {
// depending what you would like here,
// just keep the reference, or create new object
if (false && item.constructor) {
// would not advice to do that, reason? Read below
result = new item.constructor();
} else {
result = item;
}
}
} else {
result = item;
}
}
return result;
}
var copy = clone({
one : {
'one-one' : new String("hello"),
'one-two' : [
"one", "two", true, "four"
]
},
two : document.createElement("div"),
three : [
{
name : "three-one",
number : new Number("100"),
obj : new function() {
this.name = "Object test";
}
}
]
})
And now, let's talk about problems you might get when start cloning REAL objects. I'm talking now, about objects which you create by doing something like
var User = function(){}
var newuser = new User();
Of course you can clone them, it's not a problem, every object expose constructor property, and you can use it to clone objects, but it will not always work. You also can do simple for in on this objects, but it goes to the same direction - trouble. I have also included clone functionality inside the code, but it's excluded by if( false ) statement.
So, why cloning can be a pain? Well, first of all, every object/instance might have some state. You never can be sure that your objects doesn't have for example an private variables, and if this is the case, by cloning object, you just break the state.
Imagine there is no state, that's fine. Then we still have another problem. Cloning via "constructor" method will give us another obstacle. It's an arguments dependency. You never can be sure, that someone who created this object, did not did, some kind of
new User({
bike : someBikeInstance
});
If this is the case, you are out of luck, someBikeInstance was probably created in some context and that context is unkown for clone method.
So what to do? You still can do for in solution, and treat such objects like normal object literals, but maybe it's an idea not to clone such objects at all, and just pass the reference of this object?
Another solution is - you could set a convention that all objects which must be cloned should implement this part by themselves and provide appropriate API method ( like cloneObject ). Something what cloneNode is doing for DOM.
You decide.
The JSON.parse(JSON.stringify()) combination to deep copy Javascript objects is an ineffective hack, as it was meant for JSON data. It does not support values of undefined or function () {}, and will simply ignore them (or null them) when "stringifying" (marshalling) the Javascript object into JSON.
A better solution is to use a deep copy function. The function below deep copies objects, and does not require a 3rd party library (jQuery, LoDash, etc).
function copy(aObject) {
// Prevent undefined objects
// if (!aObject) return aObject;
let bObject = Array.isArray(aObject) ? [] : {};
let value;
for (const key in aObject) {
// Prevent self-references to parent object
// if (Object.is(aObject[key], aObject)) continue;
value = aObject[key];
bObject[key] = (typeof value === "object") ? copy(value) : value;
}
return bObject;
}
Note: This code can check for simple self-references (uncomment the section // Prevent self-references to parent object), but you should also avoid creating objects with self-references when possible. Please see: https://softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references
There is now structuredClone in the Web API which also works with circular references.
Previous answer
Here is an ES6 function that will also work for objects with cyclic references:
function deepClone(obj, hash = new WeakMap()) {
if (Object(obj) !== obj) return obj; // primitives
if (hash.has(obj)) return hash.get(obj); // cyclic reference
const result = obj instanceof Set ? new Set(obj) // See note about this!
: obj instanceof Map ? new Map(Array.from(obj, ([key, val]) =>
[key, deepClone(val, hash)]))
: obj instanceof Date ? new Date(obj)
: obj instanceof RegExp ? new RegExp(obj.source, obj.flags)
// ... add here any specific treatment for other classes ...
// and finally a catch-all:
: obj.constructor ? new obj.constructor()
: Object.create(null);
hash.set(obj, result);
return Object.assign(result, ...Object.keys(obj).map(
key => ({ [key]: deepClone(obj[key], hash) }) ));
}
// Sample data
var p = {
data: 1,
children: [{
data: 2,
parent: null
}]
};
p.children[0].parent = p;
var q = deepClone(p);
console.log(q.children[0].parent.data); // 1
A note about Sets and Maps
How to deal with the keys of Sets and Maps is debatable: those keys are often primitives (in which case there is no debate), but they can also be objects. In that case the question becomes: should those keys be cloned?
One could argue that this should be done, so that if those objects are mutated in the copy, the objects in the original are not affected, and vice versa.
On the other hand one would want that if a Set/Map has a key, this should be true in both the original and the copy -- at least before any change is made to either of them. It would be strange if the copy would be a Set/Map that has keys that never occurred before (as they were created during the cloning process): surely that is not very useful for any code that needs to know whether a given object is a key in that Set/Map or not.
As you notice, I am more of the second opinion: the keys of Sets and Maps are values (maybe references) that should remain the same.
Such choices will often also surface with other (maybe custom) objects. There is no general solution, as much depends on how the cloned object is expected to behave in your specific case.
we can achieve deep clone by using structuredClone()
const original = { name: "stack overflow" };
// Clone it
const clone = structuredClone(original);
The Underscore.js contrib library library has a function called snapshot that deep clones an object
snippet from the source:
snapshot: function(obj) {
if(obj == null || typeof(obj) != 'object') {
return obj;
}
var temp = new obj.constructor();
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
temp[key] = _.snapshot(obj[key]);
}
}
return temp;
}
once the library is linked to your project, invoke the function simply using
_.snapshot(object);
Lo-Dash, now a superset of Underscore.js, has a couple of deep clone functions:
_.cloneDeep(object)
_.cloneDeepWith(object, (val) => {if(_.isElement(val)) return val.cloneNode(true)})
the second parameter is a function that is invoked to produce the cloned value.
From an answer of the author himself:
lodash underscore build is provided to ensure compatibility with the latest stable version of Underscore.
As others have noted on this and similar questions, cloning an "object", in the general sense, is dubious in JavaScript.
However, there is a class of objects, which I call "data" objects, that is, those constructed simply from { ... } literals and/or simple property assignments or deserialized from JSON for which it is reasonable to want to clone. Just today I wanted to artificially inflate data received from a server by 5x to test what happens for a large data set, but the object (an array) and its children had to be distinct objects for things to function correctly. Cloning allowed me to do this to multiply my data set:
return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta));
The other place I often end up cloning data objects is for submitting data back to the host where I want to strip state fields from the object in the data model before sending it. For example, I might want to strip all fields starting with "_" from the object as it is cloned.
This is the code I ended up writing to do this generically, including supporting arrays and a selector to choose which members to clone (which uses a "path" string to determine context):
function clone(obj,sel) {
return (obj ? _clone("",obj,sel) : obj);
}
function _clone(pth,src,sel) {
var ret=(src instanceof Array ? [] : {});
for(var key in src) {
if(!src.hasOwnProperty(key)) { continue; }
var val=src[key], sub;
if(sel) {
sub+=pth+"/"+key;
if(!sel(sub,key,val)) { continue; }
}
if(val && typeof(val)=='object') {
if (val instanceof Boolean) { val=Boolean(val); }
else if(val instanceof Number ) { val=Number (val); }
else if(val instanceof String ) { val=String (val); }
else { val=_clone(sub,val,sel); }
}
ret[key]=val;
}
return ret;
}
The simplest reasonable deep-clone solution, assuming a non-null root object and with no member selection is:
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;
}
This is the deep cloning method I use, I think it
Great, hope you make suggestions
function deepClone (obj) {
var _out = new obj.constructor;
var getType = function (n) {
return Object.prototype.toString.call(n).slice(8, -1);
}
for (var _key in obj) {
if (obj.hasOwnProperty(_key)) {
_out[_key] = getType(obj[_key]) === 'Object' || getType(obj[_key]) === 'Array' ? deepClone(obj[_key]) : obj[_key];
}
}
return _out;
}
The below function is most efficient way to deep clone javascript objects.
function deepCopy(obj){
if (!obj || typeof obj !== "object") return obj;
var retObj = {};
for (var attr in obj){
var type = obj[attr];
switch(true){
case (type instanceof Date):
var _d = new Date();
_d.setDate(type.getDate())
retObj[attr]= _d;
break;
case (type instanceof Function):
retObj[attr]= obj[attr];
break;
case (type instanceof Array):
var _a =[];
for (var e of type){
//_a.push(e);
_a.push(deepCopy(e));
}
retObj[attr]= _a;
break;
case (type instanceof Object):
var _o ={};
for (var e in type){
//_o[e] = type[e];
_o[e] = deepCopy(type[e]);
}
retObj[attr]= _o;
break;
default:
retObj[attr]= obj[attr];
}
}
return retObj;
}
var obj = {
string: 'test',
array: ['1'],
date: new Date(),
object:{c: 2, d:{e: 3}},
function: function(){
return this.date;
}
};
var copyObj = deepCopy(obj);
console.log('object comparison', copyObj === obj); //false
console.log('string check', copyObj.string === obj.string); //true
console.log('array check', copyObj.array === obj.array); //false
console.log('date check', copyObj2.date === obj.date); //false
console.log('object check', copyObj.object === obj.object); //false
console.log('function check', copyObj.function() === obj.function()); //true
Avoid use this method
let cloned = JSON.parse(JSON.stringify(objectToClone));
Why? this method will convert 'function,undefined' to null
const myObj = [undefined, null, function () {}, {}, '', true, false, 0, Symbol];
const IsDeepClone = JSON.parse(JSON.stringify(myObj));
console.log(IsDeepClone); //[null, null, null, {…}, "", true, false, 0, null]
try to use deepClone function.There are several above
There should be no real world need for such a function anymore. This is mere academic interest.
As purely an exercise, this is a more functional way of doing it. It's an extension of #tfmontague's answer as I'd suggested adding a guard block there. But seeing as I feel compelled to ES6 and functionalise all the things, here's my pimped version. It complicates the logic as you have to map over the array and reduce over the object, but it avoids any mutations.
const cloner = (x) => {
const recurseObj = x => (typeof x === 'object') ? cloner(x) : x
const cloneObj = (y, k) => {
y[k] = recurseObj(x[k])
return y
}
// Guard blocks
// Add extra for Date / RegExp if you want
if (!x) {
return x
}
if (Array.isArray(x)) {
return x.map(recurseObj)
}
return Object.keys(x).reduce(cloneObj, {})
}
const tests = [
null,
[],
{},
[1,2,3],
[1,2,3, null],
[1,2,3, null, {}],
[new Date('2001-01-01')], // FAIL doesn't work with Date
{x:'', y: {yx: 'zz', yy: null}, z: [1,2,3,null]},
{
obj : new function() {
this.name = "Object test";
}
} // FAIL doesn't handle functions
]
tests.map((x,i) => console.log(i, cloner(x)))
my addition to all the answers
function deepCopy(arr) {
if (typeof arr !== 'object') return arr
if (Array.isArray(arr)) return [...arr].map(deepCopy)
for (const prop in arr)
copy[prop] = deepCopy(arr[prop])
return copy
}
My solution, deep clones objects, arrays and functions.
let superClone = (object) => {
let cloning = {};
Object.keys(object).map(prop => {
if(Array.isArray(object[prop])) {
cloning[prop] = [].concat(object[prop])
} else if(typeof object[prop] === 'object') {
cloning[prop] = superClone(object[prop])
} else cloning[prop] = object[prop]
})
return cloning
}
example
let obj = {
a: 'a',
b: 'b',
c: {
deep: 'try and copy me',
d: {
deeper: 'try me again',
callDeeper() {
return this.deeper
}
},
arr: [1, 2, 3]
},
hi() {
return this.a
}
};
const cloned = superClone(obj)
obj.a = 'A'
obj.c.deep = 'i changed'
obj.c.arr = [45,454]
obj.c.d.deeper = 'i changed'
console.log(cloned) // unchanged object
If your objects contain methods don't use JSON to deep clone, JSON deep cloning doesn't clone methods.
If you take a look at this, object person2 only clones the name, not person1's greet method.
const person1 = {
name: 'John',
greet() {
return `HI, ${this.name}`
}
}
const person2 = JSON.parse(JSON.stringify(person1))
console.log(person2) // { name: 'John' }
Deep cloning of the object can be done in several ways but each having their own limitations as mentioned below. Hence, I will suggest you to use structuredClone algorithm.
JSON.parse(JSON.stringify(object)) - won't copy functions, Dates, undefineds & many more.
const obj = {
name: 'alpha',
printName: function() {
console.log(this.name);
}
};
console.log(JSON.parse(JSON.stringify(obj))); // function not copied
_.cloneDeep(object) - It is a good option but requires lodash.
const obj = {
name: 'alpha',
printName: function() {
console.log(this.name);
}
};
filteredArray = _.cloneDeep(obj);
console.log(filteredArray)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.2.1/lodash.min.js"></script>
structuredClone(object) - Browser Native API (It is good to use as JSON.parse() and JSON.stringify() do not serialize the circular object or things like Map, Set, Date, RegEx etc.)
const a = { x: 20, date: new Date() };
a.c = a;
console.log(structuredClone(a)); // { x: 20, date: <date object>, c: <circular ref> }
console.log(JSON.parse(JSON.stringify(a))); // throwing a TypeError
I noticed that Map should require special treatment, thus with all suggestions in this thread, code will be:
function deepClone( obj ) {
if( !obj || true == obj ) //this also handles boolean as true and false
return obj;
var objType = typeof( obj );
if( "number" == objType || "string" == objType ) // add your immutables here
return obj;
var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor();
if( obj instanceof Map )
for( var key of obj.keys() )
result.set( key, deepClone( obj.get( key ) ) );
for( var key in obj )
if( obj.hasOwnProperty( key ) )
result[key] = deepClone( obj[ key ] );
return result;
}
This works for arrays, objects and primitives. Doubly recursive algorithm that switches between two traversal methods:
const deepClone = (objOrArray) => {
const copyArray = (arr) => {
let arrayResult = [];
arr.forEach(el => {
arrayResult.push(cloneObjOrArray(el));
});
return arrayResult;
}
const copyObj = (obj) => {
let objResult = {};
for (key in obj) {
if (obj.hasOwnProperty(key)) {
objResult[key] = cloneObjOrArray(obj[key]);
}
}
return objResult;
}
const cloneObjOrArray = (el) => {
if (Array.isArray(el)) {
return copyArray(el);
} else if (typeof el === 'object') {
return copyObj(el);
} else {
return el;
}
}
return cloneObjOrArray(objOrArray);
}
We can utilize recursion for making deepCopy. It can create copy of array, object, array of object, object with function.
if you want, you can add function for other type of data structure like map etc.
function deepClone(obj) {
var retObj;
_assignProps = function(obj, keyIndex, retObj) {
var subType = Object.prototype.toString.call(obj[keyIndex]);
if(subType === "[object Object]" || subType === "[object Array]") {
retObj[keyIndex] = deepClone(obj[keyIndex]);
}
else {
retObj[keyIndex] = obj[keyIndex];
}
};
if(Object.prototype.toString.call(obj) === "[object Object]") {
retObj = {};
for(key in obj) {
this._assignProps(obj, key, retObj);
}
}
else if(Object.prototype.toString.call(obj) == "[object Array]") {
retObj = [];
for(var i = 0; i< obj.length; i++) {
this._assignProps(obj, i, retObj);
}
};
return retObj;
};
Use immutableJS
import { fromJS } from 'immutable';
// An object we want to clone
let objA = {
a: { deep: 'value1', moreDeep: {key: 'value2'} }
};
let immB = fromJS(objA); // Create immutable Map
let objB = immB.toJS(); // Convert to plain JS object
console.log(objA); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }
console.log(objB); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }
// objA and objB are equalent, but now they and their inner objects are undependent
console.log(objA === objB); // false
console.log(objA.a === objB.a); // false
console.log(objA.moreDeep === objB.moreDeep); // false
Or lodash/merge
import merge from 'lodash/merge'
var objA = {
a: [{ 'b': 2 }, { 'd': 4 }]
};
// New deeply cloned object:
merge({}, objA );
// We can also create new object from several objects by deep merge:
var objB = {
a: [{ 'c': 3 }, { 'e': 5 }]
};
merge({}, objA , objB ); // Object { a: [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
This one, using circular reference, works for me
//a test-object with circular reference :
var n1 = { id:0, text:"aaaaa", parent:undefined}
var n2 = { id:1, text:"zzzzz", parent:undefined }
var o = { arr:[n1,n2], parent:undefined }
n1.parent = n2.parent = o;
var obj = { a:1, b:2, o:o }
o.parent = obj;
function deepClone(o,output){
if(!output) output = {};
if(o.______clone) return o.______clone;
o.______clone = output.______clone = output;
for(var z in o){
var obj = o[z];
if(typeof(obj) == "object") output[z] = deepClone(obj)
else output[z] = obj;
}
return output;
}
console.log(deepClone(obj));
var newDate = new Date(this.oldDate);
I was passing oldDate to function and generating newDate from this.oldDate, but it was changing this.oldDate also.So i used that solution and it worked.
This solution will avoid recursion problems when using [...target] or {...target}
function shallowClone(target) {
if (typeof a == 'array') return [...target]
if (typeof a == 'object') return {...target}
return target
}
/* set skipRecursion to avoid throwing an exception on recursive references */
/* no need to specify refs, or path -- they are used interally */
function deepClone(target, skipRecursion, refs, path) {
if (!refs) refs = []
if (!path) path = ''
if (refs.indexOf(target) > -1) {
if (skipRecursion) return null
throw('Recursive reference at ' + path)
}
refs.push(target)
let clone = shallowCopy(target)
for (i in target) target[i] = deepClone(target, refs, path + '.' + i)
return clone
}
Hello i just wanted to post my answer since i think its more readable. Note:this doesnt cover classes since i dont use them but you can easily add a condition for that
/** Copies any type of object/array of objects
* #param obj The object to be copied
* #param customKeys A list of keys that are to be excluded from deepCopy (optional)
*/
export function deepCopyObject(obj: any, customKeys?: Array<string|number|symbol>) {
if (obj == undefined)
return;
if (typeof obj !== 'object')
return obj;
if (typeof obj === 'function')
return obj;
const isArray = obj.length > -1;
if (isArray)
return copyArray(obj);
const isObjectDate = obj instanceof Date;
if(isObjectDate)
return new Date(obj);
const isDOM = obj.nodeType && typeof obj.cloneNode == "function";
if (isDOM)
return obj.cloneNode(true);
const isHtmlComponent = obj.$$typeof != undefined; // you can pass html/react components and maybe setup a custom function to copy them
if (isHtmlComponent)
return obj;
const newObject = <typeof obj>{};
const keys = Object.keys(obj);
keys.forEach((key: keyof (typeof obj)) => {
newObject[key] = copyKeysOfTypeObject(obj, key, customKeys);
})
const cantAccessObjectKeys = keys.lenght ==0; // ex: window.navigator
if (cantAccessObjectKeys)
return obj;
return newObject
}
function copyArray(arr: any) {
const newArr = new Array(0);
arr.forEach((obj: any) => {
newArr.push(deepCopyObject(obj));
})
return newArr;
}
function copyKeysOfTypeObject(obj: any, key: string | number | symbol, customKeys?: Array<string | number | symbol>) {
if (!key)
return;
if (customKeys && customKeys.includes(key))
return obj[key];
return deepCopyObject(obj[key]);
}
structuredClone now is supported by most the browsers
its main limitation is about DONT coping functions. It would require some extra work to copy/move it manually.
We can at least copy classes in an easy way by adding the prototypes later
const proto = Object.getPrototypeOf(object)
const newObject = structuredClone(object)
Object.setPrototypeOf(newObject, proto)
let obj1 = {
a: 100,
b: {
c: 200,
d: [1, 2, 3],
e: () => {}
}
}
function deepClone(obj) {
let newObj = {};
for (let key in obj) {
let val = obj[key];
if (val instanceof Array) {
newObj[key] = [...val]
} else if (typeof val === 'object') {
newObj[key] = deepClone(val)
} else {
newObj[key] = val;
}
}
return newObj;
}
obj2 = deepClone(obj1);
obj1.b.c = 300;
console.log(obj1);
console.log(obj2);

Categories