JavaScript: Deep Copy Circular JSON - javascript

intro:
I'm trying to write a deep copy method, but need to keep track of my visited nodes, so that I can link to the previously visitedNode instead of deep copying forever until stack overflow.
attempts:
var visitedNodes = {};
var obj = {}; obj.a = obj; // circular; can't use JSON.stringify)
var obj2 = {};
visitedNodes[obj] = "should need key obj (not obj2) to access this string";
console.log(visitedNodes[obj2]); // logs the string unfortunately
I don't have a unique way of storing the memory location -- it stores itself at [object Object] and I can't use JSON.stringify because it's a circular structure
I tried using var visitedNodes = new Map(); but still no dice
My current approach is to utilize the Array.prototype.indexOf function, but I don't know if it works with circular structures either, because I'm getting a stack overflow here too!!!
this.clone = function (item, visitedNodes) {
visitedNodes = visitedNodes || [];
if (typeof item === "object" && !Array.isArray(item)) {
if (visitedNodes.indexOf(item) === -1) {
var cloneObject = {};
visitedNodes.push(cloneObject);
for (var i in item) {
if (item.hasOwnProperty(i)) {
cloneObject[i] = this.clone(item[i], visitedNodes);
}
}
return cloneObject;
} else {
return visitedNodes[visitedNodes.indexOf(item)];
}
}
else if (typeof item === "object" && Array.isArray(item)) {
if (visitedNodes.indexOf(item) === -1) {
var cloneArray = [];
visitedNodes.push(cloneArray);
for (var j = 0; j < item.length; j++) {
cloneArray.push(this.clone(item[j], visitedNodes));
}
return cloneArray;
} else {
return visitedNodes[visitedNodes.indexOf(item)];
}
}
return item; // not object, not array, therefore primitive
};
the question:
Anyone have any ideas for getting a unique memory address so I can determine if I've been to the Object's reference before? I believe I could construct a unique hash based off Object.keys() and Object.prototype.constructor but that seems absurd and will give false positives if constructor is the same and the child keys are the same as the parent

In visitedNodes save the original reference, and create another array to save with same index the clone object to use when is a reference.
function deepClone(obj) {
var visitedNodes = [];
var clonedCopy = [];
function clone(item) {
if (typeof item === "object" && !Array.isArray(item)) {
if (visitedNodes.indexOf(item) === -1) {
visitedNodes.push(item);
var cloneObject = {};
clonedCopy.push(cloneObject);
for (var i in item) {
if (item.hasOwnProperty(i)) {
cloneObject[i] = clone(item[i]);
}
}
return cloneObject;
} else {
return clonedCopy[visitedNodes.indexOf(item)];
}
}
else if (typeof item === "object" && Array.isArray(item)) {
if (visitedNodes.indexOf(item) === -1) {
var cloneArray = [];
visitedNodes.push(item);
clonedCopy.push(cloneArray);
for (var j = 0; j < item.length; j++) {
cloneArray.push(clone(item[j]));
}
return cloneArray;
} else {
return clonedCopy[visitedNodes.indexOf(item)];
}
}
return item; // not object, not array, therefore primitive
}
return clone(obj);
}
var obj = {b: 'hello'};
obj.a = { c: obj };
var dolly = deepClone(obj);
obj.d = 'hello2';
console.log(obj);
console.log(dolly);
the code running example:
http://jsbin.com/favekexiba/1/watch?js,console

The code in the answer of Fetz works great but breaks on Date objects. Here is a patched version:
const visitedNodes = [];
const clonedCopy = [];
function clone(item) {
if (typeof item === 'object') {
if (item instanceof Date) { // Date
if (visitedNodes.indexOf(item) === -1) {
visitedNodes.push(item);
var cloneObject = new Date(item);
clonedCopy.push(cloneObject);
return cloneObject;
}
return clonedCopy[visitedNodes.indexOf(item)];
} else if (XMLDocument && item instanceof XMLDocument) { // XML Document
if (visitedNodes.indexOf(item) === -1) {
visitedNodes.push(item);
const cloneObject = item.implementation.createDocument(item.documentElement.namespaceURI, null, null);
const newNode = cloneObject.importNode(item.documentElement, true);
cloneObject.appendChild(newNode);
clonedCopy.push(cloneObject);
return cloneObject;
}
return clonedCopy[visitedNodes.indexOf(item)];
} else if (!Array.isArray(item)) { // Object
if (visitedNodes.indexOf(item) === -1) {
visitedNodes.push(item);
var cloneObject = {};
clonedCopy.push(cloneObject);
for (const i in item) {
if (item.hasOwnProperty(i)) {
cloneObject[i] = clone(item[i]);
}
}
return cloneObject;
}
return clonedCopy[visitedNodes.indexOf(item)];
} else if (Array.isArray(item)) { // Array
if (visitedNodes.indexOf(item) === -1) {
const cloneArray = [];
visitedNodes.push(item);
clonedCopy.push(cloneArray);
for (let j = 0; j < item.length; j++) {
cloneArray.push(clone(item[j]));
}
return cloneArray;
}
return clonedCopy[visitedNodes.indexOf(item)];
}
}
return item; // not date, not object, not array, therefore primitive
}
return clone(obj);
I would've preferred to edit the answer of Fetz but the edit queue is full.
edit 19/07/2017: Added XML Document cloning as well

Related

Javascript json add a child element dynamically [duplicate]

Suppose we are only given
var obj = {};
var propName = "foo.bar.foobar";
How can we set the property obj.foo.bar.foobar to a certain value (say "hello world")?
So I want to achieve this, while we only have the property name in a string:
obj.foo.bar.foobar = "hello world";
function assign(obj, prop, value) {
if (typeof prop === "string")
prop = prop.split(".");
if (prop.length > 1) {
var e = prop.shift();
assign(obj[e] =
Object.prototype.toString.call(obj[e]) === "[object Object]"
? obj[e]
: {},
prop,
value);
} else
obj[prop[0]] = value;
}
var obj = {},
propName = "foo.bar.foobar";
assign(obj, propName, "Value");
I know it's an old one, but I see only custom functions in answers.
If you don't mind using a library, look at lodash _.set and _.get function.
Since this question appears to be answered by incorrect answers, I'll just refer to the correct answer from a similar question
function setDeepValue(obj, value, path) {
if (typeof path === "string") {
var path = path.split('.');
}
if(path.length > 1){
var p=path.shift();
if(obj[p]==null || typeof obj[p]!== 'object'){
obj[p] = {};
}
setDeepValue(obj[p], value, path);
}else{
obj[path[0]] = value;
}
}
Use:
var obj = {};
setDeepValue(obj, 'Hello World', 'foo.bar.foobar');
edit: I've created a jsPerf.com testcase to compare the accepted answer with my version.
Turns out that my version is faster, especially when you go very deep.
http://jsfiddle.net/9YMm8/
var nestedObjectAssignmentFor = function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
tmpObj = tmpObj[propNames[i]] = i !== propLength ? {} : value;
}
return obj;
}
var obj = nestedObjectAssignment({},"foo.bar.foobar","hello world");
​
​
All solutions overid any of the original data when setting so I have tweaked with the following, made it into a single object too:
var obj = {}
nestObject.set(obj, "a.b", "foo");
nestObject.get(obj, "a.b"); // returns foo
var nestedObject = {
set: function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if (i === propLength){
if(tmpObj[propNames[i]]){
tmpObj[propNames[i]] = value;
}else{
tmpObj[propNames[i]] = value;
}
}else{
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
tmpObj = tmpObj[propNames[i]] = {};
}
}
}
return obj;
},
get: function(obj, propString){
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
break;
}
}
return tmpObj;
}
};
Can also change functions to be an Oject.prototype method changing obj param to this:
Object.prototype = { setNested = function(){ ... }, getNested = function(){ ... } }
{}.setNested('a.c','foo')
Here is a get and set function i just compiled from a couple of threads + some custom code.
It will also create keys that don't exist on set.
function setValue(object, path, value) {
var a = path.split('.');
var o = object;
for (var i = 0; i < a.length - 1; i++) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
}
o[a[a.length - 1]] = value;
}
function getValue(object, path) {
var o = object;
path = path.replace(/\[(\w+)\]/g, '.$1');
path = path.replace(/^\./, '');
var a = path.split('.');
while (a.length) {
var n = a.shift();
if (n in o) {
o = o[n];
} else {
return;
}
}
return o;
}
Here is a simple function to do that using reference.
function setValueByPath (obj, path, value) {
var ref = obj;
path.split('.').forEach(function (key, index, arr) {
ref = ref[key] = index === arr.length - 1 ? value : {};
});
return obj;
}
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
var fullPath = path.split('.'),
way = fullPath.slice(),
last = way.pop();
way.reduce(function (r, a) {
return r[a] = r[a] || {};
}, object)[last] = value;
}
var object = {},
propName = 'foo.bar.foobar',
value = 'hello world';
setValue(object, propName, value);
console.log(object);
Here's one that returns the updated object
function deepUpdate(value, path, tree, branch = tree) {
const last = path.length === 1;
branch[path[0]] = last ? value : branch[path[0]];
return last ? tree : deepUpdate(value, path.slice(1), tree, branch[path[0]]);
}
const path = 'cat.dog';
const updated = deepUpdate('a', path.split('.'), {cat: {dog: null}})
// => { cat: {dog: 'a'} }
A very straightforward one.
This implementation should be very performant.
It avoids recursions, and function calls, while maintaining simplicity.
/**
* Set the value of a deep property, creating new objects as necessary.
* #param {Object} obj The object to set the value on.
* #param {String|String[]} path The property to set.
* #param {*} value The value to set.
* #return {Object} The object at the end of the path.
* #author github.com/victornpb
* #see https://stackoverflow.com/a/46060952/938822
* #example
* setDeep(obj, 'foo.bar.baz', 'quux');
*/
function setDeep(obj, path, value) {
const props = typeof path === 'string' ? path.split('.') : path;
for (var i = 0, n = props.length - 1; i < n; ++i) {
obj = obj[props[i]] = obj[props[i]] || {};
}
obj[props[i]] = value;
return obj;
}
/*********************** EXAMPLE ***********************/
const obj = {
hello : 'world',
};
setDeep(obj, 'root', true);
setDeep(obj, 'foo.bar.baz', 1);
setDeep(obj, ['foo','quux'], '😉');
console.log(obj);
// ⬇︎ Click "Run" below to see output
I was looking for an answer that does not overwrite existing values and was easily readable and was able to come up with this. Leaving this here in case it helps others with the same needs
function setValueAtObjectPath(obj, pathString, newValue) {
// create an array (pathComponents) of the period-separated path components from pathString
var pathComponents = pathString.split('.');
// create a object (tmpObj) that references the memory of obj
var tmpObj = obj;
for (var i = 0; i < pathComponents.length; i++) {
// if not on the last path component, then set the tmpObj as the value at this pathComponent
if (i !== pathComponents.length-1) {
// set tmpObj[pathComponents[i]] equal to an object of it's own value
tmpObj[pathComponents[i]] = {...tmpObj[pathComponents[i]]}
// set tmpObj to reference tmpObj[pathComponents[i]]
tmpObj = tmpObj[pathComponents[i]]
// else (IS the last path component), then set the value at this pathComponent equal to newValue
} else {
// set tmpObj[pathComponents[i]] equal to newValue
tmpObj[pathComponents[i]] = newValue
}
}
// return your object
return obj
}
Same as Rbar's answers, very useful when you're working with redux reducers. I use lodash clone instead of spread operator to support arrays too:
export function cloneAndPatch(obj, path, newValue, separator='.') {
let stack = Array.isArray(path) ? path : path.split(separator);
let newObj = _.clone(obj);
obj = newObj;
while (stack.length > 1) {
let property = stack.shift();
let sub = _.clone(obj[property]);
obj[property] = sub;
obj = sub;
}
obj[stack.shift()] = newValue;
return newObj;
}
Object.getPath = function(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
};
Object.setPath = function(o, p, v) {
var a = p.split('.');
var o = o;
for (var i = 0; i < a.length - 1; i++) {
if (a[i].indexOf('[') === -1) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
} else {
// Not totaly optimised
var ix = a[i].match(/\[.*?\]/g)[0];
var n = a[i].replace(ix, '');
o = o[n][ix.substr(1,ix.length-2)]
}
}
if (a[a.length - 1].indexOf('[') === -1) {
o[a[a.length - 1]] = v;
} else {
var ix = a[a.length - 1].match(/\[.*?\]/g)[0];
var n = a[a.length - 1].replace(ix, '');
o[n][ix.substr(1,ix.length-2)] = v;
}
};
Here's a simple method that uses a scoped Object that recursively set's the correct prop by path.
function setObjectValueByPath(pathScope, value, obj) {
const pathStrings = pathScope.split('/');
obj[pathStrings[0]] = pathStrings.length > 1 ?
setObjectValueByPath(
pathStrings.splice(1, pathStrings.length).join('/'),
value,
obj[pathStrings[0]]
) :
value;
return obj;
}
How about a simple and short one?
Object.assign(this.origin, { [propName]: value })
You can use reduce : (you can test it by copy/paste on browser console)
const setValueOf = (obj, value, ...path) => {
path.reduce((o, level, idx) => {
if(idx === path.length -1) { o[level] = value }; // on last change the value of the prop
return o && o[level]; // return the prop
}, obj);
};
Example
let objExmp = {a: 'a', b: {b1: 'b1', b2: 'b2', b3: { b3_3 : 'default_value' } }};
setValueOf(objExmp, 'new_value' , 'b', 'b3', 'b3_3');
console.log('objExmp', objExmp); // prop changed to 'new_value'
You can split the string path by '.' and spread like :
setValueOf(objExmp, 'new_value' , ...'b.b3.b3_3'.split('.'));

JS update Array through string eval [duplicate]

Suppose we are only given
var obj = {};
var propName = "foo.bar.foobar";
How can we set the property obj.foo.bar.foobar to a certain value (say "hello world")?
So I want to achieve this, while we only have the property name in a string:
obj.foo.bar.foobar = "hello world";
function assign(obj, prop, value) {
if (typeof prop === "string")
prop = prop.split(".");
if (prop.length > 1) {
var e = prop.shift();
assign(obj[e] =
Object.prototype.toString.call(obj[e]) === "[object Object]"
? obj[e]
: {},
prop,
value);
} else
obj[prop[0]] = value;
}
var obj = {},
propName = "foo.bar.foobar";
assign(obj, propName, "Value");
I know it's an old one, but I see only custom functions in answers.
If you don't mind using a library, look at lodash _.set and _.get function.
Since this question appears to be answered by incorrect answers, I'll just refer to the correct answer from a similar question
function setDeepValue(obj, value, path) {
if (typeof path === "string") {
var path = path.split('.');
}
if(path.length > 1){
var p=path.shift();
if(obj[p]==null || typeof obj[p]!== 'object'){
obj[p] = {};
}
setDeepValue(obj[p], value, path);
}else{
obj[path[0]] = value;
}
}
Use:
var obj = {};
setDeepValue(obj, 'Hello World', 'foo.bar.foobar');
edit: I've created a jsPerf.com testcase to compare the accepted answer with my version.
Turns out that my version is faster, especially when you go very deep.
http://jsfiddle.net/9YMm8/
var nestedObjectAssignmentFor = function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
tmpObj = tmpObj[propNames[i]] = i !== propLength ? {} : value;
}
return obj;
}
var obj = nestedObjectAssignment({},"foo.bar.foobar","hello world");
​
​
All solutions overid any of the original data when setting so I have tweaked with the following, made it into a single object too:
var obj = {}
nestObject.set(obj, "a.b", "foo");
nestObject.get(obj, "a.b"); // returns foo
var nestedObject = {
set: function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if (i === propLength){
if(tmpObj[propNames[i]]){
tmpObj[propNames[i]] = value;
}else{
tmpObj[propNames[i]] = value;
}
}else{
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
tmpObj = tmpObj[propNames[i]] = {};
}
}
}
return obj;
},
get: function(obj, propString){
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
break;
}
}
return tmpObj;
}
};
Can also change functions to be an Oject.prototype method changing obj param to this:
Object.prototype = { setNested = function(){ ... }, getNested = function(){ ... } }
{}.setNested('a.c','foo')
Here is a get and set function i just compiled from a couple of threads + some custom code.
It will also create keys that don't exist on set.
function setValue(object, path, value) {
var a = path.split('.');
var o = object;
for (var i = 0; i < a.length - 1; i++) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
}
o[a[a.length - 1]] = value;
}
function getValue(object, path) {
var o = object;
path = path.replace(/\[(\w+)\]/g, '.$1');
path = path.replace(/^\./, '');
var a = path.split('.');
while (a.length) {
var n = a.shift();
if (n in o) {
o = o[n];
} else {
return;
}
}
return o;
}
Here is a simple function to do that using reference.
function setValueByPath (obj, path, value) {
var ref = obj;
path.split('.').forEach(function (key, index, arr) {
ref = ref[key] = index === arr.length - 1 ? value : {};
});
return obj;
}
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
var fullPath = path.split('.'),
way = fullPath.slice(),
last = way.pop();
way.reduce(function (r, a) {
return r[a] = r[a] || {};
}, object)[last] = value;
}
var object = {},
propName = 'foo.bar.foobar',
value = 'hello world';
setValue(object, propName, value);
console.log(object);
Here's one that returns the updated object
function deepUpdate(value, path, tree, branch = tree) {
const last = path.length === 1;
branch[path[0]] = last ? value : branch[path[0]];
return last ? tree : deepUpdate(value, path.slice(1), tree, branch[path[0]]);
}
const path = 'cat.dog';
const updated = deepUpdate('a', path.split('.'), {cat: {dog: null}})
// => { cat: {dog: 'a'} }
A very straightforward one.
This implementation should be very performant.
It avoids recursions, and function calls, while maintaining simplicity.
/**
* Set the value of a deep property, creating new objects as necessary.
* #param {Object} obj The object to set the value on.
* #param {String|String[]} path The property to set.
* #param {*} value The value to set.
* #return {Object} The object at the end of the path.
* #author github.com/victornpb
* #see https://stackoverflow.com/a/46060952/938822
* #example
* setDeep(obj, 'foo.bar.baz', 'quux');
*/
function setDeep(obj, path, value) {
const props = typeof path === 'string' ? path.split('.') : path;
for (var i = 0, n = props.length - 1; i < n; ++i) {
obj = obj[props[i]] = obj[props[i]] || {};
}
obj[props[i]] = value;
return obj;
}
/*********************** EXAMPLE ***********************/
const obj = {
hello : 'world',
};
setDeep(obj, 'root', true);
setDeep(obj, 'foo.bar.baz', 1);
setDeep(obj, ['foo','quux'], '😉');
console.log(obj);
// ⬇︎ Click "Run" below to see output
I was looking for an answer that does not overwrite existing values and was easily readable and was able to come up with this. Leaving this here in case it helps others with the same needs
function setValueAtObjectPath(obj, pathString, newValue) {
// create an array (pathComponents) of the period-separated path components from pathString
var pathComponents = pathString.split('.');
// create a object (tmpObj) that references the memory of obj
var tmpObj = obj;
for (var i = 0; i < pathComponents.length; i++) {
// if not on the last path component, then set the tmpObj as the value at this pathComponent
if (i !== pathComponents.length-1) {
// set tmpObj[pathComponents[i]] equal to an object of it's own value
tmpObj[pathComponents[i]] = {...tmpObj[pathComponents[i]]}
// set tmpObj to reference tmpObj[pathComponents[i]]
tmpObj = tmpObj[pathComponents[i]]
// else (IS the last path component), then set the value at this pathComponent equal to newValue
} else {
// set tmpObj[pathComponents[i]] equal to newValue
tmpObj[pathComponents[i]] = newValue
}
}
// return your object
return obj
}
Same as Rbar's answers, very useful when you're working with redux reducers. I use lodash clone instead of spread operator to support arrays too:
export function cloneAndPatch(obj, path, newValue, separator='.') {
let stack = Array.isArray(path) ? path : path.split(separator);
let newObj = _.clone(obj);
obj = newObj;
while (stack.length > 1) {
let property = stack.shift();
let sub = _.clone(obj[property]);
obj[property] = sub;
obj = sub;
}
obj[stack.shift()] = newValue;
return newObj;
}
Object.getPath = function(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
};
Object.setPath = function(o, p, v) {
var a = p.split('.');
var o = o;
for (var i = 0; i < a.length - 1; i++) {
if (a[i].indexOf('[') === -1) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
} else {
// Not totaly optimised
var ix = a[i].match(/\[.*?\]/g)[0];
var n = a[i].replace(ix, '');
o = o[n][ix.substr(1,ix.length-2)]
}
}
if (a[a.length - 1].indexOf('[') === -1) {
o[a[a.length - 1]] = v;
} else {
var ix = a[a.length - 1].match(/\[.*?\]/g)[0];
var n = a[a.length - 1].replace(ix, '');
o[n][ix.substr(1,ix.length-2)] = v;
}
};
Here's a simple method that uses a scoped Object that recursively set's the correct prop by path.
function setObjectValueByPath(pathScope, value, obj) {
const pathStrings = pathScope.split('/');
obj[pathStrings[0]] = pathStrings.length > 1 ?
setObjectValueByPath(
pathStrings.splice(1, pathStrings.length).join('/'),
value,
obj[pathStrings[0]]
) :
value;
return obj;
}
How about a simple and short one?
Object.assign(this.origin, { [propName]: value })
You can use reduce : (you can test it by copy/paste on browser console)
const setValueOf = (obj, value, ...path) => {
path.reduce((o, level, idx) => {
if(idx === path.length -1) { o[level] = value }; // on last change the value of the prop
return o && o[level]; // return the prop
}, obj);
};
Example
let objExmp = {a: 'a', b: {b1: 'b1', b2: 'b2', b3: { b3_3 : 'default_value' } }};
setValueOf(objExmp, 'new_value' , 'b', 'b3', 'b3_3');
console.log('objExmp', objExmp); // prop changed to 'new_value'
You can split the string path by '.' and spread like :
setValueOf(objExmp, 'new_value' , ...'b.b3.b3_3'.split('.'));

Javascript array with associative array difference

Is there a way to return the difference between two arrays in JavaScript?
I can not use indexOf in this case.
For example:
var a1 = [{"a":"A"},{"b":"B"}];
var a2 = [{"a":"A"},{"b":"B"},{"c":"C"}];
// need [{"c":"C"}]
Please advise.
One object can never be the same as another object even if they have the same content. They would still be different instances of Objects.
That means you have to compare keys and values to check that they match, or in this case, don't match.
var a1 = [{"a":"A"},{"b":"B"}];
var a2 = [{"a":"A"},{"b":"B"},{"c":"C"}];
var a3 = a2.filter(function(o) {
return Object.keys(o).some(function(k) {
return a1.every(function(o2) {
return !(k in o2) || (o2[k] != o[k]);
});
});
});
FIDDLE
As I mentioned in my comment, objects are only equal if they refer to the same instance. Therefore, any built-in system will not do, least of all == and ===. So, first you must define your own comparison function.
Let's say that two objects are equal if they contain the same keys with the same values.
function areObjectsEqual(a,b) {
function helper(a,b) {
var k;
for( k in a) {
if( a.hasOwnProperty(k)) {
if( !b.hasOwnProperty(k)) return false;
if( typeof a[k] != typeof b[k]) return false;
if( typeof a[k] == "object") {
if( !areObjectsEqual(a[k],b[k])) return false;
// the above line allows handling of nested objects
}
else {
if( a[k] != b[k]) return false;
// this comparison is technically strict
// because we already checked typeof earlier
}
}
}
}
return helper(a,b) && helper(b,a);
}
Okay, now that that's out of the way, we can compare our functions.
function array_diff(a,b) {
var result = [], l = a.length, i, m = b.length, j;
outer:
for( i=0; i<l; i++) {
for( j=0; j<m; j++) {
if( typeof a[i] != typeof b[j]) continue;
if( typeof a[i] == "object") {
if( !areObjectsEqual(a[i],b[j])) continue;
}
else {
if( a[i] != b[j]) continue;
}
// if we got to here, it's a match!
// ... so actually we want to skip over the result :p
continue outer;
}
// okay, if we get HERE then there was no match,
// because we skipped the "continue outer"
result.push(a[i]);
}
return result;
}
And there you go!
Easy and simple way to achieve your goal
var a1 = [{"a":"A"},{"b":"B"}];
var a2 = [{"a":"A"},{"c":"C"},{"b":"B"}];
var max = (a1.length > a2.length) ? a1 : a2;
var min = (a1.length > a2.length) ? a2 : a1;
var newArray = [];
for ( var i = 0; i < max.length; i++ ) { // saving elements into string
max[i] = JSON.stringify(max[i]);
if ( typeof min[i] !== undefined ) {
min[i] = JSON.stringify(min[i]);
}
}
for ( var i = 0; i < max.length; i++ ) { // checking values uniqueness
if ( min.indexOf(max[i]) === -1 ) {
newArray.push(max[i]);
}
}
// if you need new Array's elements back in object do following iteration
for ( var i in newArray ) { // loop recreate results array's elements into object again
newArray[i] = JSON.parse(newArray[i]);
}
console.log(newArray); // result : [Object { c="C"}]
JSFiddle
var a1 = [{"a":"A"},{"b":"B"}];
var a2 = [{"a":"A"},{"b":"B"},{"c":"C"}];
var obj = {}, result = [];
function updateObjectCount(currentItem) {
var keys, key;
for (key in currentItem) {
if (currentItem.hasOwnProperty(key)) {
keys = key;
break;
}
}
obj[key] = obj[key] || {};
obj[key][currentItem[key]] = (obj[key][currentItem[key]] || 0) + 1;
}
a1.forEach(updateObjectCount);
a2.forEach(updateObjectCount);
for (var key1 in obj) {
if (obj.hasOwnProperty((key1))) {
for (var key2 in obj[key1]) {
if (obj.hasOwnProperty((key1))) {
if (obj[key1][key2] === 1) {
var temp = {};
temp[key1] = key2;
result.push(temp)
}
}
}
}
}
console.log(result);
# [ { c: 'C' } ]

Access nested objects via array of property names

Say I have an object like this (simplified):
var options = {
boxes: {
size: {
x: 15,
y: 18
},
shadow: {
[...]
}
};
And I have an array of names:
var names = ['boxes', 'size', 'x'];
What is an easy way to get/set a value inside the object according to the array, in this example it would be:
options.boxes.size.x = somevalue;
Any ideas?
There's no easy, built-in method for doing this. You'd have to write your own method:
function getPath(obj, props) {
for(var i = 0; i < props.length; i++) {
if (props[i] in obj) {
obj = obj[props[i]];
} else {
return; // not found
}
}
return obj;
}
function setPath(obj, value, props) {
for(var i = 0; i < props.length - 1; i++) {
if (props[i] in obj) {
obj = obj[props[i]];
} else {
return; // not found
}
}
obj[props[i]] = value;
}
alert(getPath(options, names)); // 15
setPath(options, 25, names);
alert(getPath(options, names)); // 25
Just use a loop that iterates the names and grabs the next nested object for the current name. Either a falsey value or the end of the array should halt the loop.
var obj = options;
var i = 0;
while (obj && i < names.length)
obj = obj[names[i++]];
Or simply use .reduce()
names.reduce(function(obj, name) {
return obj && obj[name];
}, options);
And of course you can name and reuse the function if you prefer.
function toPropertyIn(obj, name) {
return obj && obj[name];
}
names.reduce(toPropertyIn, options);
To make a getter/setter:
function nestedProp(obj, names, value) {
if (arguments.length > 1)
var setProp = names.pop();
var res = names.reduce(function(obj, name) {
return obj && obj[name];
}, options);
if (res && setProp !== undefined)
res[setProp] = value;
else
return res;
}
nestedProp(options, names, "foo"); // to set
var val = nestedProp(options, names); // to get

Set value in JSON by a path using lodash or underscore

I wanna set the value in a JSON using a path string like this "a.0.b" for a JSON that looks like this:
{
a: [
{
b: 'c'
}
]
}
I came up with this solution but I wonder if there is a simpler way to write this:
function setValue(path, value, json) {
var keys = path.split('.');
_.reduce(keys, function(obj, key, i) {
if (i === keys.length - 1) {
obj[key] = value;
} else {
return obj[key];
}
}, json);
}
so calling setValue('a.0.b', 'd', {a:[{b:'c'}]}) would change the json to {a:[{b:'d'}]}
Here's a solution. I benchmarked the two possible solutions and it seems looping over object and path is faster than using the reduce function. See the JSPerf tests here: http://jsperf.com/set-value-in-json-by-a-path-using-lodash-or-underscore
function setValue(path, val, obj) {
var fields = path.split('.');
var result = obj;
for (var i = 0, n = fields.length; i < n && result !== undefined; i++) {
var field = fields[i];
if (i === n - 1) {
result[field] = val;
} else {
if (typeof result[field] === 'undefined' || !_.isObject(result[field])) {
result[field] = {};
}
result = result[field];
}
}
}

Categories

Resources