Reuven Karasik
Reuven Karasik

Reputation: 484

Object Comparing: check if an object contains the whole other object

I have two objects. Their structure looks a bit like this:

{
 education: ["school", "institute"],
 courses: ["HTML", "JS", "CSS"],
 Computer: {
        "OS":"WXP",
        "WS":"NotePad"
         }
 }

The second:

{
 education: ["school", "university", "institute", "collage"],
 courses: ["HTML", "CSS", "JS", "Managing", "Directing"],
 Computer: {
        "OS":"WXP",
        "WS":"NotePad",
        "AV":"Avast"
         },
 something: function(){...},
 other: "thing"

}

As you may noticed, the second object containes the whole first object, plus some items that the first one doesn't have.
I need to compare these two objects, and get an answer(true-false) if the second objects containes every single item of the first object.
true - if all of the items of the first object are also in the second one
false - if at least one of the items of the first object is not also in the second one, for example: if the second object wouldn't have the "css" course.

(The first one is requirements, the second is what the person has. I need to check if the person has all of the requirements)

Could be plain JS, jQuery, whatever. I prefer not to use server-side languages for that.

is there a way of doing that?

THANKS!

Upvotes: 5

Views: 7615

Answers (5)

Krasimir Koeff
Krasimir Koeff

Reputation: 51

In addition to Benjamin's answer - you could test this:

const sub = (big, small) => {
    if (typeof big === 'function' || typeof small === 'string') return small === big; // function or string reference equality
    if (big && big.length) { // iterable, for example array, nodelist etc. (even string!)
        if (small.length > big.length) return false; // small is bigger!
        for (let i = 0; i < small.length; i++)
            if (!sub(big[i], small[i])) // doesn't have a property
                return false;
        return true; // all properties are subproperties recursively
    }
    if (typeof big === 'object' && big !== null) {
        // I assume null is not a subset of an object, you may change this, it's conceptual
        if (typeof small !== 'object' || small === null) return false;
        // console.log(Object.keys(small));
        for (const key of Object.keys(small)) {
            // I consider the prototype a part of the object, you may filter this with a
            // hasOwnProperty check here.
            if (sub(big[key], small[key]) === false) // doesn't have a property
                return false;
            continue;
        }
        return true;
    }
    return big === small; // primitive value type equality
};

or even use a much cleaner solution: https://github.com/blackflux/object-deep-contain

Upvotes: 0

Abhishek Kumar
Abhishek Kumar

Reputation: 1

// When order of objects is not same

function isContainedIn(a, b) {
    if (typeof a != typeof b)
        return false;
    if (Array.isArray(a) && Array.isArray(b)) {
        if(a.length == 1) {
            var j=0;
            while (j < b.length) {
                if ((isContainedIn( a[0], b[j]))) {
                    return true;
                }
                j++;
            }
            return false;
        } else {
            var k=0;
            while (k < a.length) {
                if (!(isContainedIn([a[k]], b))) {
                    return false;
                }
                k++;
            }
            return true;
        }
    } else if (Object(a) === a) {
        for (var p in a)
            if (!(p in b && isContainedIn(a[p], b[p])))
                return false;
        return true;
    } else
        return a === b;
};


isContainedIn(requirements, person)
true

Upvotes: 0

Bergi
Bergi

Reputation: 664425

Just recursively check it:

function isContainedIn(a, b) {
    if (typeof a != typeof b)
        return false;
    if (Array.isArray(a) && Array.isArray(b)) {
        // assuming same order at least
        for (var i=0, j=0, la=a.length, lb=b.length; i<la && j<lb;j++)
            if (isContainedIn(a[i], b[j]))
                i++;
        return i==la;
    } else if (Object(a) === a) {
        for (var p in a)
            if (!(p in b && isContainedIn(a[p], b[p])))
                return false;
        return true;
    } else
        return a === b;
}

> isContainedIn(requirements, person)
true

For a more set-logic-like approach to arrays, where order does not matter, add something like

        a.sort();
        b = b.slice().sort()

(assuming orderable contents) before the array comparison loop or replace that by the quite inefficient

        return a.every(function(ael) {
            return b.some(function(bel) {
                return isContainedIn(ael, bel);
            });
        });

Upvotes: 6

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276296

JavaScript (in ES5) has two composite native types (I'm assuming you don't have any custom collections in your code, if you do - I assume they support the 'old' iteration protocol (having .length)

Here is an annotated sketch of a solution. I did not run this - it's there to get you an idea of how to implement this algorithm. Note that this enters an endless loop for back references (var a = {}; a.a =a}).

function sub(big,small){
    if(typeof big === "function") return small === big; // function reference equality.
    if(big.length){ // iterable, for example array, nodelist etc. (even string!)
        if(small.length > big.length) return false; // small is bigger!
        for(var i = 0; i < small.length; i++ ){
            if(!sub(big[i],small[i])){ // doesn't have a property
                return false;
            }
        }
        return true; // all properties are subproperties recursively
    }
    if(typeof big === "object" && big !== null){
        // I assume null is not a subset of an object, you may change this, it's conceptual
        if(typeof small !== "object" || small === null) return false; 
        for(var key in small){
            // I consider the prototype a part of the object, you may filter this with a 
            // hasOwnProperty check here.
            if(!sub(big[key],small[key])){ // doesn't have a property
                 return false;
            }
            return true;
        }
    }
    return big === small; // primitive value type equality
                          // , or ES7 value type equality, future compat ftw :P
}

Upvotes: 3

Mosho
Mosho

Reputation: 7078

Edit: didn't notice that merge changes the first argument... changed the code, but it still would cause obj2 to change. You can add _.cloneDeep(obj2) which should take care of that, but by then my solution doesn't seem as elegant. Updated the demo with cloneDeep as well.

Edit2: Since JSON.stringify requires the order of object properties to be the same in the objects you compare, you could instead use something like Object comparison in JavaScript. However, in the demo you can see that it works, so I would say there is a good chance that for your case, using _.merge with JSON.stringify is reliable.

With lo-dash, you can use _.merge and check whether the result is the same as the larger object.

function(obj1, obj2) {
    var obj3 =_.merge(_.cloneDeep(obj2), obj1);
    return JSON.stringify(obj3) === JSON.stringify(obj1);
}

demo

Of course, another option would be to iterate over the entire object with vanilla JS.

Upvotes: 0

Related Questions