Deep cloning object (JS) [duplicate] - javascript

This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 4 years ago.
how can I deep clone an object, what could be wrong with this solution.
I wrote this decision, but I'm not sure if this is good, and what bottlenecks it has.
How to do it correctly on vanilla js, without using jQuery. If the object has (enumerable: false)?
let user = {
name: 'SomeName',sayHi: function(){console.log(this.name);}}
Object.defineProperty(user, 'sayHi', {enumerable:false});
function deepCloneNew(obj){
if (!obj) { return };
let cloneObj = {};
let keys = Object.getOwnPropertyNames(obj);
keys.forEach((key)=>{
if(typeof obj[key] === 'object' && obj[key] !== null){
deepCloneNew(obj[key]);
}
if(typeof obj[key] === 'function'){
Object.defineProperty(cloneObj, key, Object.getOwnPropertyDescriptor(obj, key));
}
if(typeof obj[key] !== 'object' && typeof obj[key] !== 'function' || obj[key] === null){
Object.defineProperty(cloneObj, key, Object.getOwnPropertyDescriptor(obj, key));
}
})
return cloneObj;
}
let copy = deepCloneNew(user);

Please Follow this
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") {
var 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;
}

Related

How to check if object is empty in javascript for all levels in object

I wanted to find out if my object is empty or not for all its nested objects and key-value pairs.
for e.g.,
const x = {
a:"",
b:[],
c:{
x:[]
},
d:{
x:{
y:{
z:""
}
}
}
};
this should be an empty object and if any of this contains single value then it should be non empty.
Here is the way to do what using recursion
const x = {
a:"",
b:[],
c:{
x:[]
},
d:{
x:{
y:{
z:''
}
}
}
};
function checkEmpty(obj){
for(let key in obj){
//if the value is 'object'
if(obj[key] instanceof Object === true){
if(checkEmpty(obj[key]) === false) return false;
}
//if value is string/number
else{
//if array or string have length is not 0.
if(obj[key].length !== 0) return false;
}
}
return true;
}
console.log(checkEmpty(x))
x.d.x.y.z = 0;
console.log(checkEmpty(x));
You can write a recursive function like following. Function creates a set with 2 possible values true and false. If the size of set is 1 and the value being false, which mean that the object is empty.
const x = {a:"",b:[],c:{x:[]},d:{x:{y:{z:""}}}};
function isEmpty(o, r = new Set()) {
for (let k in o) {
if(typeof o[k] === "object") {
if(Array.isArray(o[k])) r.add(!!o[k].length);
else isEmpty(o[k],r);
} else r.add(!(o[k] === "" || o[k] === undefined || o[k] === null));
}
return r;
}
let result = isEmpty(x);
console.log(result.has(false) && result.size == 1);
I will use a recursive approach for this one, we iterate over the object.keys() and check is every value related to the key is empty, in the case the value is an object, we go one level deeper to check it.
const x = {
a:"",
b:[],
c:{x:[]},
d:{x:{y:{z:""}}}
};
const x1 = [0,0,0];
const x2 = {0:0,1:0,2:0};
const isEmpty = (obj, empty=true) =>
{
Object.keys(obj).forEach((key) =>
{
if (typeof obj[key] === "object")
empty = isEmpty(obj[key], empty);
else
empty = empty && (obj[key].length === 0);
// Return early if we detect empty here.
if (!empty) return empty;
});
return empty;
}
console.log("original x: ", isEmpty(x));
x.a = "I'm not empty";
console.log("x after edit: ", isEmpty(x));
console.log("x1: ", isEmpty(x1));
console.log("x2: ", isEmpty(x2));
try (we use here recursion, fat arrow, obj. keys, reduce, ternary operator and object checking)
let isEmpty = o => o.constructor.name === "Object" ?
Object.keys(o).reduce((y,z)=> y&&isEmpty(o[z]) ,true) : o.length == 0;
const x = {
a:"",
b:[],
c:{
x:[]
},
d:{
x:{
y:{
z:""
}
}
}
};
let isEmpty = o => o.constructor.name === "Object" ?
Object.keys(o).reduce((y,z)=> y&&isEmpty(o[z]) ,true) : o.length == 0;
// Test
console.log(isEmpty(x));
x.d.x.y.z="Smile to life and life will smile to you";
console.log(isEmpty(x));

Find a key in nested object and replace its value - Javascript

Here is my fiddle : DEMO
By recursive iterations, I am able to find the key in object2 object3 and replace its value by the values from data2 data3 objects.
However, I am unable to replace the value if it is an array. (key called 'coordinates' in this case)
How could this be fixed?
function update(object, data) {
function getAllKeys(o) {
Object.keys(o).forEach(function(k) {
if (typeof o[k] === 'object') {
return getAllKeys(o[k]);
}
keys[k] = o;
});
}
var keys = Object.create(null);
getAllKeys(object);
Object.keys(data).forEach(function(k) {
if (keys[k] && k in keys[k]) { // check if key for update exist
keys[k][k] = data[k];
}
});
}
Update the getAllKeys method with:
function getAllKeys(o) {
Object.keys(o).forEach(function(k) {
contains_object = Array.isArray(o[k]) && o[k].some(val=> { return typeof val == "object" && !Array.isArray(val); });
if ((Array.isArray(o[k]) && !contains_object) || typeof o[k] !== 'object') {
keys[k] = o;
} else {
return getAllKeys(o[k]);
}
keys[k] = o;
});
}
Note: !(o[k] instanceof Array) - http://jsfiddle.net/08pnu7rx/1/
The problem is that typeof also returns object for arrays.
You want to change your function to still assign the key when the object is an array.
function getAllKeys(o) {
Object.keys(o).forEach(function(k) {
if (Array.isArray(o[k]) || typeof o[k] !== 'object') {
keys[k] = o;
} else {
return getAllKeys(o[k]);
}
});
}
Notice I swapped around the logic, so you first check for either an array or another non-object type. If that check passes, you assign the value. If not, you recurse.
You should note that this is not at all specific to arrays. You will have similar problems if you have a nested property that is a Date, for example.
The problem in your code is that typeof o[k] === 'object' returns true even it o[k] is an array which is the case for coordinated, You need a negative check for array too like
Object.keys(o).forEach(function(k) {
if (typeof o[k] === 'object'&& !Array.isArray(o[k])) {
return getAllKeys(o[k]);
}
keys[k] = o;
});
Working fiddle
According to the docs or typeof:
// use Array.isArray or Object.prototype.toString.call
// to differentiate regular objects from arrays
typeof [1, 2, 4] === 'object';

Converting lodash's `_.pull` to vanilla JS? [duplicate]

This question already has answers here:
Remove empty elements from an array in Javascript
(49 answers)
Closed 5 years ago.
I'm having an issue with this function that recursively removes empty values from an object:
const _ = require('lodash')
function sanitize(object) {
Object.entries(object).forEach(([key, val]) => {
if (
val == null ||
Number.isNaN(val) ||
(typeof val === 'string' && isOnlyWhitespace(val)) ||
(typeof val === 'object' && Object.keys(sanitize(val)).length === 0)
) {
delete object[key]
}
});
// Remove `undefined` values leftover from using `delete` on an array.
if (Array.isArray(object)) {
_.pull(object, undefined); // THIS IS THE LINE IM TRYING TO CHANGE
}
return object;
}
function isOnlyWhitespace(str) {
return !(/\S/).test(str.trim());
}
I'm trying to replace _.pull(object, undefined) with vanilla JS, but nothing seems to give the right output (I've tried using stuff like filter.)
Here is a snippet you can run to see both outputs:
// LODASH VERSION
function lodashSanitize(object) {
Object.entries(object).forEach(([key, val]) => {
if (
val == null ||
Number.isNaN(val) ||
(typeof val === 'string' && isOnlyWhitespace(val)) ||
(typeof val === 'object' && Object.keys(lodashSanitize(val)).length === 0)
) {
delete object[key]
}
});
// Remove `undefined` values leftover from using `delete` on an array.
if (Array.isArray(object)) {
_.pull(object, undefined); // THIS IS THE LINE IM TRYING TO CHANGE
}
return object;
}
// MY VERSION
function mySanitize(object) {
Object.entries(object).forEach(([key, val]) => {
if (
val == null ||
Number.isNaN(val) ||
(typeof val === 'string' && isOnlyWhitespace(val)) ||
(typeof val === 'object' && Object.keys(mySanitize(val)).length === 0)
) {
delete object[key]
}
});
// Remove `undefined` values leftover from using `delete` on an array.
if (Array.isArray(object)) {
object = object.filter(val => val != null) // THIS IS MY ATTEMPT
}
return object;
}
function isOnlyWhitespace(str) {
return !(/\S/).test(str.trim());
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<button id="lodash">Show lodash output</button>
<button id="me">Show my output</button>
<p id="output" />
<script>
/**
* Fiddle-related code, you can ignore this
*/
const lodashBtn = document.querySelector('#lodash')
const meBtn = document.querySelector('#me')
const output = document.querySelector('#output')
function createExampleInput() {
const input = {
name: 'John',
grades: [
90,
undefined,
50,
null
]
};
return input;
}
lodashBtn.addEventListener('click', () => {
output.textContent = JSON.stringify(lodashSanitize(createExampleInput()), null, 4)
});
meBtn.addEventListener('click', () => {
output.textContent = JSON.stringify(mySanitize(createExampleInput()), null, 4)
});
</script>
The problem is that filter returns a new array. Why not just use a for loop and splice:
if (Array.isArray(object)) {
for (var i = object.length - 1; i >= 0; i--) {
if (object[i] === undefined) {
object.splice(i, 1);
}
}
}

Resolve circular references from JSON object

If I have a serialized JSON from json.net like so:
User:{id:1,{Foo{id:1,prop:1}},
FooList{$ref: "1",Foo{id:2,prop:13}}
I want to have knockout output a foreach over FooList but I am not sure how to proceed because the $ref things could throw things.
I'm thinking the solution would be to somehow force all the Foos to be rendered in the FooList by not using:
PreserveReferencesHandling = PreserveReferencesHandling.Objects
but that seems wasteful..
I've found some bugs and implemented arrays support:
function resolveReferences(json) {
if (typeof json === 'string')
json = JSON.parse(json);
var byid = {}, // all objects by id
refs = []; // references to objects that could not be resolved
json = (function recurse(obj, prop, parent) {
if (typeof obj !== 'object' || !obj) // a primitive value
return obj;
if (Object.prototype.toString.call(obj) === '[object Array]') {
for (var i = 0; i < obj.length; i++)
// check also if the array element is not a primitive value
if (typeof obj[i] !== 'object' || !obj[i]) // a primitive value
continue;
else if ("$ref" in obj[i])
obj[i] = recurse(obj[i], i, obj);
else
obj[i] = recurse(obj[i], prop, obj);
return obj;
}
if ("$ref" in obj) { // a reference
var ref = obj.$ref;
if (ref in byid)
return byid[ref];
// else we have to make it lazy:
refs.push([parent, prop, ref]);
return;
} else if ("$id" in obj) {
var id = obj.$id;
delete obj.$id;
if ("$values" in obj) // an array
obj = obj.$values.map(recurse);
else // a plain object
for (var prop in obj)
obj[prop] = recurse(obj[prop], prop, obj);
byid[id] = obj;
}
return obj;
})(json); // run it!
for (var i = 0; i < refs.length; i++) { // resolve previously unknown references
var ref = refs[i];
ref[0][ref[1]] = byid[ref[2]];
// Notice that this throws if you put in a reference at top-level
}
return json;
}
The json object which you are receiving from the server contains Circular References. Before using the object you should have to first remove all the $ref properties from the object, means in place of $ref : "1" you have to put the object which this link points.
In your case may be it is pointing to the User's object whose id is 1
For this you should check out Douglas Crockfords Plugin on github.There is a cycle.js which can do the job for you.
or you can use the following code (not tested) :
function resolveReferences(json) {
if (typeof json === 'string')
json = JSON.parse(json);
var byid = {}, // all objects by id
refs = []; // references to objects that could not be resolved
json = (function recurse(obj, prop, parent) {
if (typeof obj !== 'object' || !obj) // a primitive value
return obj;
if ("$ref" in obj) { // a reference
var ref = obj.$ref;
if (ref in byid)
return byid[ref];
// else we have to make it lazy:
refs.push([parent, prop, ref]);
return;
} else if ("$id" in obj) {
var id = obj.$id;
delete obj.$id;
if ("$values" in obj) // an array
obj = obj.$values.map(recurse);
else // a plain object
for (var prop in obj)
obj[prop] = recurse(obj[prop], prop, obj)
byid[id] = obj;
}
return obj;
})(json); // run it!
for (var i=0; i<refs.length; i++) { // resolve previously unknown references
var ref = refs[i];
ref[0][ref[1]] = byid[refs[2]];
// Notice that this throws if you put in a reference at top-level
}
return json;
}
Let me know if it helps !
This is actually extremely simple if you take advantage of JSON.parse's reviver parameter.
Example below. See browser console for the output because StackOverflow's snippet console output will not provide an accurate picture of what the result is.
// example JSON
var j = '{"$id":"0","name":"Parent",' +
'"child":{"$id":"1", "name":"Child","parent":{"$ref":"0"}},' +
'"nullValue":null}';
function parseAndResolve(json) {
var refMap = {};
return JSON.parse(json, function (key, value) {
if (key === '$id') {
refMap[value] = this;
// return undefined so that the property is deleted
return void(0);
}
if (value && value.$ref) { return refMap[value.$ref]; }
return value;
});
}
console.log(parseAndResolve(j));
<b>See actual browser console for output.</b>
I had trouble with the array correction in the answer of Alexander Vasiliev.
I can't comment his answer (don't own enough reputations points ;-) ), so I had to add a new answer...
(where I had a popup as best practice not to answer on other answers and only on the original question - bof)
if (Object.prototype.toString.call(obj) === '[object Array]') {
for (var i = 0; i < obj.length; i++) {
// check also if the array element is not a primitive value
if (typeof obj[i] !== 'object' || !obj[i]) // a primitive value
return obj[i];
if ("$ref" in obj[i])
obj[i] = recurse(obj[i], i, obj);
else
obj[i] = recurse(obj[i], prop, obj);
}
return obj;
}
In the accepted implementation, if you're inspecting an array and come across a primitive value, you will return that value and overwrite that array. You want to instead continue inspecting all of the elements of the array and return the array at the end.
function resolveReferences(json) {
if (typeof json === 'string')
json = JSON.parse(json);
var byid = {}, // all objects by id
refs = []; // references to objects that could not be resolved
json = (function recurse(obj, prop, parent) {
if (typeof obj !== 'object' || !obj) // a primitive value
return obj;
if (Object.prototype.toString.call(obj) === '[object Array]') {
for (var i = 0; i < obj.length; i++)
// check also if the array element is not a primitive value
if (typeof obj[i] !== 'object' || !obj[i]) // a primitive value
continue;
else if ("$ref" in obj[i])
obj[i] = recurse(obj[i], i, obj);
else
obj[i] = recurse(obj[i], prop, obj);
return obj;
}
if ("$ref" in obj) { // a reference
var ref = obj.$ref;
if (ref in byid)
return byid[ref];
// else we have to make it lazy:
refs.push([parent, prop, ref]);
return;
} else if ("$id" in obj) {
var id = obj.$id;
delete obj.$id;
if ("$values" in obj) // an array
obj = obj.$values.map(recurse);
else // a plain object
for (var prop in obj)
obj[prop] = recurse(obj[prop], prop, obj);
byid[id] = obj;
}
return obj;
})(json); // run it!
for (var i = 0; i < refs.length; i++) { // resolve previously unknown references
var ref = refs[i];
ref[0][ref[1]] = byid[ref[2]];
// Notice that this throws if you put in a reference at top-level
}
return json;
}
my solution(works for arrays as well):
usage: rebuildJsonDotNetObj(jsonDotNetResponse)
The code:
function rebuildJsonDotNetObj(obj) {
var arr = [];
buildRefArray(obj, arr);
return setReferences(obj, arr)
}
function buildRefArray(obj, arr) {
if (!obj || obj['$ref'])
return;
var objId = obj['$id'];
if (!objId)
{
obj['$id'] = "x";
return;
}
var id = parseInt(objId);
var array = obj['$values'];
if (array && Array.isArray(array)) {
arr[id] = array;
array.forEach(function (elem) {
if (typeof elem === "object")
buildRefArray(elem, arr);
});
}
else {
arr[id] = obj;
for (var prop in obj) {
if (typeof obj[prop] === "object") {
buildRefArray(obj[prop], arr);
}
}
}
}
function setReferences(obj, arrRefs) {
if (!obj)
return obj;
var ref = obj['$ref'];
if (ref)
return arrRefs[parseInt(ref)];
if (!obj['$id']) //already visited
return obj;
var array = obj['$values'];
if (array && Array.isArray(array)) {
for (var i = 0; i < array.length; ++i)
array[i] = setReferences(array[i], arrRefs)
return array;
}
for (var prop in obj)
if (typeof obj[prop] === "object")
obj[prop] = setReferences(obj[prop], arrRefs)
delete obj['$id'];
return obj;
}

How to copy or duplicate an array of arrays

I'm trying to make a function that duplicates an array of arrays. I tried blah.slice(0); but it only copies the references. I need to make a duplicate that leaves the original intact.
I found this prototype method at http://my.opera.com/GreyWyvern/blog/show.dml/1725165
Object.prototype.clone = function() {
var newObj = (this instanceof Array) ? [] : {};
for (i in this) {
if (i == 'clone') continue;
if (this[i] && typeof this[i] == "object") {
newObj[i] = this[i].clone();
} else newObj[i] = this[i]
} return newObj;
};
It works, but messes up a jQuery plugin I'm using - so I need to turn it onto a function... and recursion isn't my strongest.
Your help would be appreciated!
Cheers,
function clone (existingArray) {
var newObj = (existingArray instanceof Array) ? [] : {};
for (i in existingArray) {
if (i == 'clone') continue;
if (existingArray[i] && typeof existingArray[i] == "object") {
newObj[i] = clone(existingArray[i]);
} else {
newObj[i] = existingArray[i]
}
}
return newObj;
}
For example:
clone = function(obj) {
if (!obj || typeof obj != "object")
return obj;
var isAry = Object.prototype.toString.call(obj).toLowerCase() == '[object array]';
var o = isAry ? [] : {};
for (var p in obj)
o[p] = clone(obj[p]);
return o;
}
improved as per comments

Categories