Check if two objects have common sub-objects - javascript

I need to check if two object have common sub-objects.
By common I mean that it exact same value, and not just equal values.
Something like:
function haveCommonObects(value1, value2) {
...
}
var common = {};
haveCommonObjects({a: common}, {b: {c: common}}) // true
haveCommonObjects({a: 1}, {b: 1}) // false
I need to check large objects, so function should be reasonable efficient.
Also I can't change objects, so I can't flag sub-objects with special property. Objects create in 3rd-party library so I can't alter Object.prototype.
Ideal solution would be to get some kind of ID for every object and save it in collection that support fast lookup.
Can I make such function in JS?

Here's how I would do this:
function haveCommonObjects(a,b) {
// check if a and b have any object in common, at any depth level
if (typeof(a) !== 'object' || typeof(b) !== 'object') return false;
for (var key in a) {
var o = a[key];
if (typeof(o) === 'object' && (hasObject(b,o) || haveCommonObjects(o,b)))
return true;
}
return false;
}
function hasObject(x,t) {
// check if x has a reference to object t, at any depth level
for (var key in x) {
var o = x[key];
if (typeof(o) === 'object' && (o === t || hasObject(o,t)))
return true;
}
return false;
}
function log(msg) { document.getElementById('log').innerHTML += msg+'<br/>'; }
var common = {};
log(haveCommonObjects({a: common}, {b: {c: common}})); // true
log(haveCommonObjects({a: 1}, {b: 1})); // false
<div id="log"></div>
Note: You should add a hasOwnProperty() filter in every for..in loop if you want to exclude inherited properties; see for..in and hasOwnProperty.

Related

Comparing two objects properties but not working propery

Here is my code. I know it's not entirely strict but please shed some light on why the let...in does not work properly here.
const object1 = {here: 1, object: 3};
const obj = {here: 1, object: 2};
function comp(a, b) {
if (typeof a == typeof b) {
let arra = Object.keys(a);
let arrb = Object.keys(b);
for (let key in arra){
if (a[key] == b[key]) return true
}
return false
}
}
console.log(comp(obj, object1))
The above prints true but it is supposed to print false
You're getting true because you return true in your for loop whenever a key value from one object equals the key-value pair of another object. So when your code sees the here property, it will return true, thus stopping your function from running any further code.
You need to remove this check, and only return false in your for loop, such that your for loop will only complete if it never returns (ie: all key-value pairs are equal).
Moreover, the for..in loop will loop over the keys in an object, so, there is no need to get the keys of your objects (using Object.keys) into an array (as then you'll be looping over the keys of an array (ie: the indexes)).
However, with that being said, you can use Object.keys to help with another issue. You can use it to get the number of properties in both objects, as you know the two objects are not the same if they don't have the same number of properties in them
See example below:
const object1 = {
here: 1,
object: 3
};
const obj = {
here: 1,
object: 3
};
function comp(a, b) {
if (typeof a == typeof b) {
if(Object.keys(a).length !== Object.keys(b).length) {
return false; // return false (stop fruther code execution)
}
for (let key in a) { // loop through the properties of larger object (here I've chosen 'a') - no need for Object.keys
if (a[key] != b[key])
return false; // return false (stops any further code executing)
}
return true; // we only reach this point if the for loop never returned false
}
return false; // we reach this point when the two types don't match, and so we can say they're not equal
}
console.log(comp(obj, object1))
You should use for..of (or just a plain old for) instead of for..in which is only used on Objects. You're reading array indices right now, not actual key names. Object.keys returns an Array of key names, not an Object.
Also stop returning early; Right now you return immediately after the first key check.
You need to check for actual values and not property names.
Also, you need to check if b has more properties than a, because if it is the same, but has one property more, it will still output true.
const tester = {here: 1, object: 3};
const obj1 = {here: 1, object: 2};
const obj2 = {here: 1, object: 3};
const obj3 = {here: 1, object: 3, test: 1};
const obj4 = {here: 1, test: 1};
function comp(a, b) {
if (typeof a == typeof b && Object.keys(a).length == Object.keys(b).length) { // Check for the length of the keys array, because if the length is different, they might have different properties
// Dont use Object.keys here. You don't need the keys, you need the objects
for (let key in a){
if (a[key] != b[key])
return false; // If one key property of a does not match b, return false
}
return true; // If nothing returns false, return true
}
return false; // If they are not the same type, return false
}
console.log(comp(tester, obj1))
console.log(comp(tester, obj2))
console.log(comp(tester, obj3))
console.log(comp(tester, obj4))
Here is your problem:
for (let key in arra){
if (a[key] == b[key]) return true
}
return false
}
You should perform the exact opposite while iterating through the object:
for (let key in a){ // a not arra
if (a[key] !== b[key]) return false
}
return true
}
And ommit these lines:
let arra = Object.keys(a);
let arrb = Object.keys(b);
Array.prototype.some() function can be used as below:
const object1 = {here: 1, object: 3, r:4};
const obj = {here: 1, r:4, object: 3};
function comp(a, b) {
if (typeof a == typeof b) {
let arra = Object.keys(a);
return !arra.some(key => { return a[key] != b[key] })
}
}
console.log(comp(obj, object1))

How to compare JavaScript objects obtained by parsing JSON?

wtf1 = JSON.parse('{"asdf": "jkl"}');
wtf2 = JSON.parse('{"asdf": "jkl"}');
wtf1 == wtf2; // false
wtf1 === wtf2; // false
I’ve recently stumbled upon the above problem. This counter-intuitive situation makes it hard to, for example, find a specific object in an array deep in a JSON hierarchy.
Any way to somehow compare such objects?
For simple objects you can stringify them again (with ordered keys) and compare strings. For example:
var wtf1 = JSON.parse('{"a": "a", "b": "b"}');
var wtf2 = JSON.parse('{"b": "b", "a": "a"}');
var s1 = JSON.stringify(wtf1, Object.keys(wtf1).sort());
var s2 = JSON.stringify(wtf2, Object.keys(wtf2).sort());
console.log('wtf1 is equal to wtf2: ', (s1 == s2));
For nested objects with prototypes etc you should probably use _.isEqual or any other lib that provides deep equality test.
Note, that in general it's not trivial to correctly implement deep equality test for objects, it's not as simple as iterating and comparing keys/values. However, since the "objects were obtained by parsing JSON" you can skip most of complications and recursively stringify nested values.
I think it's better to just compare them before you parse into objects. But this only works if they are exactly the same, including the order of the properties
var obj1 = {name: "potato", age: 10}
var obj2 = {name: "potato", age: 10}
console.log(obj1 == obj2) //false
console.log(JSON.stringify(obj1) == JSON.stringify(obj2)) //true
var obj1 = {name: "potato", age: 10}
var obj2 = {age: 10, name: "potato"}
console.log(obj1 == obj2) //false
console.log(JSON.stringify(obj1) == JSON.stringify(obj2)) //also false
You cannot compare objects ( as theyre differnet ), but you can iterate over each property, and compare them (and recursively check if theyre objects again):
function compare(obj1,obj2){
//check for obj2 overlapping props
if(!Object.keys(obj2).every(key=>obj1.hasOwnProperty(key))){
return false;
}
//check every key for being same
return Object.keys(obj1).every(function(key){
//if object
if((typeof obj1[key]=="object" )&&( typeof obj2[key]=="object")){
//recursively check
return compare(obj1[key],obj2[key]);
}else{
//do the normal compare
return obj1[key]===obj2[key];
}
});
}
http://jsbin.com/zagecigawi/edit?js
you can use loadash for the same.
using _.isEqual("object1", "object2");
var obj1 = {"prop1" : 2, "prop2" : 3 };
var obj2 = {"prop1" : 2, "prop2" : 3};
console.dir(_.isEqual(obj1, obj2))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
Second approach would be
JSON.stringify(obj1) === JSON.stringify(obj2)
This will compare JSON structure of response and model(It will compare keys only and not values & will work for any hierarchy)
function compareObjects(response, model) {
if (response == "" && model == "") {
return false;
}
if (typeof (response) != "object" && typeof (model) != "object") {
var response = JSON.parse(response);
var model = JSON.parse(model);
}
if (typeof (response) != typeof (model)) {
return false;
} else {
switch (Object.prototype.toString.call(model)) {
case '[object]':
var x;
var mKeys = Object.keys(model);
for (x in mKeys) {
return compareObjects(Object.keys(model)[x], Object.keys(response)[x]);
}
break;
case '[object Array]':
return compareObjects(model[0], response[0]);
case "[object String]":
return model == response;
default:
return true;
}
}
}
var response = '[{"educationId":5,"degreeName":"Bacheltecture - B.Arch"},{"educationId":2,"degreeName":"Bachelor of Arts - B.A. "},{"educationId":3,"degreeName":"Bachelor of Ayurvedic Medicine Surgery - B.A.M.S. "}]';
var model = '[{"degreeName":null},{"educationId":null,"degreeName":null},{"educationId":null,"degreeName":null}]';
var output = compareObjects(response, model);
console.log(output);
Two objects in JS even with same properties are never equal. You can stringify objects and compare those strings or iterate over all properties (but it's hard if you need deep compare)
I recommend using a library implementation for Object equality checking. Try the lodash version.
var object = { 'a': 1 };
var other = { 'a': 1 };
_.isEqual(object, other);
// => true

For in loop in recursive function not completing

I cannot get my for in loop to keep working after the first property in my object. This is a question from Eloquent JavaScript in Chapter 4:
Write a function, deepEqual, that takes two values and returns true
only if they are the same value or are objects with the same
properties whose values are also equal when compared with a recursive
call to deepEqual.
To find out whether to compare two things by identity (use the ===
operator for that) or by looking at their properties, you can use the
typeof operator. If it produces "object" for both values, you should
do a deep comparison. But you have to take one silly exception into
account: by a historical accident, typeof null also produces "object".
Here is my code:
function deepEqual(obj1, obj2) {
if ((typeof obj1 === 'object' && obj1 != null) && (typeof obj2 === 'object' && obj2 != null)) {
for (var property in obj1) {
if (property in obj2) {
return deepEqual(obj1[property], obj2[property])
} else {
return false;
}
}
} else if (obj1 !== obj2) {
return false;
} else {
return true;
}
}
var obj = {object: 3, here: 1};
var obj2 = {object: 3, here: 2};
console.log(deepEqual(obj, obj2));
The console returns true, when it should say false because the 'here' properties are not equal. When looking into the output, it's because the 'for in loop' in the function quits after the first property. Please help me as to why it's not continuing to loop.
your for loop can't move beyond the first property because you return out of the function when you call deepEqual
for (var property in obj1) {
if (property in obj2) {
// returning means no more looping....
return deepEqual(obj1[property], obj2[property])
}
you want to carry on looping if deepEqual returns that its equal, or return if its false.

Javascript - removing undefined fields from an object [duplicate]

This question already has answers here:
Remove blank attributes from an Object in Javascript
(53 answers)
Closed 5 years ago.
Is there a clean way to remove undefined fields from an object?
i.e.
> var obj = { a: 1, b: undefined, c: 3 }
> removeUndefined(obj)
{ a: 1, c: 3 }
I came across two solutions:
_.each(query, function removeUndefined(value, key) {
if (_.isUndefined(value)) {
delete query[key];
}
});
or:
_.omit(obj, _.filter(_.keys(obj), function(key) { return _.isUndefined(obj[key]) }))
A one-liner using ES6 arrow function and ternary operator:
Object.keys(obj).forEach(key => obj[key] === undefined ? delete obj[key] : {});
Or use short-circuit evaluation instead of ternary: (#Matt Langlois, thanks for the info!)
Object.keys(obj).forEach(key => obj[key] === undefined && delete obj[key])
Same example using if statement:
Object.keys(obj).forEach(key => {
if (obj[key] === undefined) {
delete obj[key];
}
});
If you want to remove the items from nested objects as well, you can use a recursive function:
const removeEmpty = (obj) => {
let newObj = {};
Object.keys(obj).forEach((key) => {
if (obj[key] === Object(obj[key])) newObj[key] = removeEmpty(obj[key]);
else if (obj[key] !== undefined) newObj[key] = obj[key];
});
return newObj;
};
I prefer to use something like Lodash:
import { pickBy, identity } from 'lodash'
const cleanedObject = pickBy(originalObject, identity)
Note that the identity function is just x => x and its result will be false for all falsy values. So this removes undefined, "", 0, null, ...
If you only want the undefined values removed you can do this:
const cleanedObject = pickBy(originalObject, v => v !== undefined)
It gives you a new object, which is usually preferable over mutating the original object like some of the other answers suggest.
Use JSON Utilities
Overview
Given an object like:
var obj = { a: 1, b: undefined, c: 3 }
To remove undefined props in an object we can use nested JSON methods stringify and parse like so:
JSON.parse(JSON.stringify(obj))
Live Example
var obj = { a: 1, b: undefined, c: 3 }
var output = JSON.parse(JSON.stringify(obj));
console.log(output)
Limitations and warnings
Depending on how Javascript is implemented.
It is possible that undefined will be converted to null instead of just being removed.
Nested Object, Array will be converted to strings
Date, time values also converted to strings
Tested
The above code was tested in Firefox, Chrome, and Node 14.18.1 and removed "b" from all obj arrays. Still I recommend exercising caution using this method unless you are in a stable environment (such as cloud functions or docker) I would not rely on this method client side.
Because it doesn't seem to have been mentioned, here's my preferred method, sans side effects or external dependencies:
const obj = {
a: 1,
b: undefined
}
const newObject = Object.keys(obj).reduce((acc, key) => {
const _acc = acc;
if (obj[key] !== undefined) _acc[key] = obj[key];
return _acc;
}, {})
console.log(newObject)
// Object {a: 1}
This solution also avoids hasOwnProperty() as Object.keys returns an array of a given object's own enumerable properties.
Object.keys(obj).forEach(function (key) {
if(typeof obj[key] === 'undefined'){
delete obj[key];
}
});
and you can add this as null or '' for stricter cleaning.
Here's a plain javascript (no library required) solution:
function removeUndefinedProps(obj) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && obj[prop] === undefined) {
delete obj[prop];
}
}
}
Working demo: http://jsfiddle.net/jfriend00/djj5g5fu/
Mhh.. I think #Damian asks for remove undefined field (property) from an JS object.
Then, I would simply do :
for (const i in myObj) {
if (typeof myObj[i] === 'undefined') {
delete myObj[i];
}
}
Short and efficient solution, in (vanilla) JS !
Example :
const myObj = {
a: 1,
b: undefined,
c: null,
d: 'hello world'
};
for (const i in myObj) {
if (typeof myObj[i] === 'undefined') {
delete myObj[i];
}
}
console.log(myObj);
This one is easy to remember, but might be slow. Use jQuery to copy non-null properties to an empty object. No deep copy unless you add true as first argument.
myObj = $.extend({}, myObj);
Another Javascript Solution
for(var i=0,keys = Object.keys(obj),len=keys.length;i<len;i++){
if(typeof obj[keys[i]] === 'undefined'){
delete obj[keys[i]];
}
}
No additional hasOwnProperty check is required as Object.keys does not look up the prototype chain and returns only the properties of obj.
DEMO

Clone object in JavaScript [duplicate]

This question already has answers here:
How do I correctly clone a JavaScript object?
(81 answers)
Closed 8 years ago.
Consider the below code or check this fiddle.
var obj = {
name: "abc",
age: 20
}
var objTwo;
console.log(obj.age);
objTwo = obj;
objTwo.age = 10;
console.log(obj.age);
I have created an object with name obj and it has two properties. Now I assign obj to another object named objTwo. Now I update one of the properties in objTwo. The same change is reflecting on obj as well. How can I assign values from one object to another without creating reference?
this will assign not by reference
<script>
var obj =
{
name: 'abc',
age: '30'
};
var objTwo = {};
for( var i in obj )
{
objTwo[i] = obj[i];
}
</script>
view fiddle
I would use jQuery to do this:
var obj1 = {
name: "abc",
age: 20
}
console.log(obj1);
var obj2 = $.extend({}, obj1, {});
console.log(obj2);
obj2.age = 1;
console.log(obj2);
console.log(obj1);
If you only need to clone simple objects, simply doing
JSON.parse (JSON.stringify (obj))
would suffice.
But this obviously doesn't work in all cases, since JSON.stringify can't handle circular references and strips out functions.
So if you want to go beyond that, things will get more complicated and you have to either rely on some utility library or need to implement your own deep clone method.
Here is a sample implementation that expects a level of deepness to clone.
(function (Object, Array) {
function cloneObject(deep, scope, clonedScope) {
var type = typeof this,
clone = {},
isCR = -1;
deep = Number(deep) || 0;
scope = scope || [];
clonedScope = clonedScope || [];
if (!Array.isArray(scope) || !Array.isArray(clonedScope) || clonedScope.length !== scope.length) {
throw new TypeError("Unexpected input");
}
//If we find a primitive, we reeturn its value.
if (type !== "object") {
return this.valueOf();
}
scope.push(this);
clonedScope.push(clone);
if (0 === deep) { //If we reached the recursion limit, we can perform a shallow copy
for (var prop in this) {
clone[prop] = this[prop];
}
} else { //Otherwise we need to make some checks first.
for (var prop in this) {
if ((isCR = scope.indexOf(this[prop])) > -1) { //If we find a circular reference, we want create a new circular reference to the cloned version.
clone[prop] = clonedScope[isCR];
} else if (typeof this[prop] !== "undefined" && this[prop] !== null) { //Otherwise continue cloning.
clone[prop] = (typeof this[prop] !== "object" ? this[prop] : this[prop].clone(deep - 1, scope, clonedScope)); //If we find a non object, we can directly assign it. Otherwise we need to recursively call the clone function, counting down the limit, and injecting the scopeArrays, to find circular references.
} else { //If the property is undefined or null, assign it as such.
clone[prop] = this[prop];
}
}
}
scope.pop(); //If we leave a recursion leve, we remove the current object from the list.
clonedScope.pop();
return clone;
}
function cloneArray(deep, scope, clonedScope) {
var clone = [];
deep = Number(deep) || 0;
scope = scope || [];
clonedScope = clonedScope || [];
if (!Array.isArray(scope) || !Array.isArray(clonedScope) || clonedScope.length !== scope.length) {
throw new TypeError("Unexpected input");
}
scope.push(this);
clonedScope.push(this);
if (0 === deep) clone = this.concat();
else this.forEach(function (e) {
if ((isCR = scope.indexOf(e)) > -1) {
clone.push(clonedScope[isCR]);
} else if (typeof e !== "undefined" && e !== null) {
clone.push((typeof e !== "object" ? e : e.clone(deep - 1, scope, clonedScope)));
} else {
clone.push(e);
}
});
scope.pop();
clonedScope.pop();
return clone;
}
Object.defineProperty(Object.prototype, "clone", {
enumerable: false,
value: cloneObject
});
Object.defineProperty(Array.prototype, "clone", {
enumerable: false,
value: cloneArray
});
})(Object, Array);
Note that extending the the built-ins prototypes is often frowned upon, however i decided to do it that way to avoid an additional typecheck and to split the logic for arrays and objects a bit more. This can easily be refactored to a normal function instead
Some tests to check that we indeed have new references.
var first = {
a: {
b: "b",
c: {
}
},
b: "asd",
c: [{}],
d: undefined,
e: null,
f: function a() {} //functions keep their original reference..
};
first.a.c.a = first.a; //Circular object reference
first.c.push(first.c); //Circular array reference
var second = first.clone(Infinity);
console.log(second, second.a === second.a.c.a, first.a !== second.a.c.a); //..., true, true.
There may be a lot of space for improvement, I particularily don't like how the scope and clonedScope get's injected. If anyone has an better idea of finding and reattaching circular references, i'd be glad to update the answer
Here is a Fiddle as well.
var obj, objTwo;
obj = {
name: "abc",
age: 20
}
console.log(obj.age);
objTwo = copy(obj);
objTwo.age = 10;
console.log(obj.age);
function copy (obj) {
var key, rtn = Object.create(Object.getPrototypeOf(obj));
for (key in obj) {
if (obj.hasOwnProperty(key)) {
rtn[key] = obj[key];
}
}
return rtn;
}
In javascript everything is passed by reference. The reason the modification "leaks" to the original function property (variable), is because you are modifying the object's property, not the object (reference) itself. Copy the object to another variable, instead of re-assigning it.
Off-topic:
In javascript everything is passed by reference. apparently is a bit contested; That's why I'm adding this addendum. Feel free to correct me if I'm wrong.
Is JavaScript a pass-by-reference or pass-by-value language? The top answer states it most clearly:
Instead, the situation is that the item passed in is passed by value.
But the item that is passed by value is itself a reference.
So my wording was confusing, but if you also keep in my mind that every operator returns a reference, and keep in mind that every assignment and parameter-passing simply copies those references returned by operators (and value-literals), then passed-by-reference kind of makes sense.
The linked top answer has the full (correct) explanation.
Okay, This might be a strange solution but it is plain javascript. If you want to clone something, I would go with the word, "deep copy", you can use JSON like this:
var obj = {
name: "abc",
age: 20
}
new_object=JSON.parse(JSON.stringify(obj));
Now, you have clone of the object obj.
Another solution is like this:
var new_obj={};
for( new_prop in obj){
if (obj.hasOwnProperty(new_prop)){
new_obj[new_prop]=obj[new_prop]
}
}

Categories

Resources