5

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!

Reuven Karasik
  • 475
  • 5
  • 14
  • 2
    `JSON.stringify(obj1.courses) == JSON.stringify(obj2.courses)` – adeneo Apr 13 '14 at 17:08
  • possible duplicate of [Object comparison in JavaScript](http://stackoverflow.com/questions/1068834/object-comparison-in-javascript) – nanobash Apr 13 '14 at 17:13
  • 2
    @adeneo This doesn't work - it checks whether the objects are identical, and we want to check whether one is a subset of the other. In fact, in theory it's not even guaranteed to return `true` if they're identical, since the fields in an object may come out in different orders. And it's also sensitive to the order of elements in an array; note that the `"courses"` array is has elements in different orders in both cases. – David Knipe Apr 13 '14 at 17:25
  • @DavidKnipe - Yeah yeah, I know, but it works in some cases, if the order, case and everything is equal etc. but it's a bad way to compare objects, and comparing objects is bad in itself as two objects are never the same anyway, one should check for certain properties or indices etc instead. – adeneo Apr 13 '14 at 17:46
  • @DavidKnipe and edeneo - thank you both. but as David said before - I'm not trying to check if they're equal, I'm trying to see if one of them has all of the items from the other object. – Reuven Karasik Apr 13 '14 at 18:00

5 Answers5

6

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);
            });
        });
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Bergi and @BenjaminGruenbaum I can see that your direction is right, but Bergi's solution returns `true` even if the person doesn't have all of the requirements, and @BenjaminGruenbaum 's solution is endless.. – Reuven Karasik Apr 14 '14 at 08:47
  • Creative use of 'Object'. I'm definitely stealing this :) – Benjamin Gruenbaum Apr 14 '14 at 09:58
  • @ReuvenKarasik: Mine will recurse endless in case of circular references as well. You'll hardly have those though. – Bergi Apr 14 '14 at 11:01
  • Yeah, I had one and when I tried to use JSON.stringify I got errors so I fixed it. But yours doesn't help either because it says `true` even if `b` doesn't have **all** of the items in object `a` – Reuven Karasik Apr 14 '14 at 12:27
  • @ReuvenKarasik: Can you make a simple (short) example where it doesn't work? – Bergi Apr 14 '14 at 12:29
  • OK, you're right, I didn't test it properly, I'm sorry. Notice that http://jsfiddle.net/kinging/rgm3v/ Works perfectly (after I made sure both arrays are sorted), and if i remove or change even one of the required items, it's false. THANK YOU! – Reuven Karasik Apr 14 '14 at 13:15
  • Yet, `!(p in b || isContainedIn())` was indeed a mistake which led to wrong results. I thought of `!(p in b) || !isContainedIn()`, and have corrected it to `&&` now. Thanks for being attentive :-) – Bergi Apr 14 '14 at 13:27
3

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
}
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Hey, a question: Where's that endless loop you mentioned and why is it endless? Thank you! – Reuven Karasik Apr 13 '14 at 19:04
  • I played with your code a bit, and I noticed that the `if(!sub(big[key],small[key]))` line is the one that makes the loop endless, I just don't quite understand WHY.I mean, the Sub function is placed insite itself, why would you do that? – Reuven Karasik Apr 13 '14 at 19:23
  • @ReuvenKarasik it's called recursion, it's a very important and very fundamental concept in programming. I define a problem by solving the same problem on smaller conditions (in the circular reference case it never gets smaller). Let's see how strong it is: I'd like you to define - as strictly as you can - what an object being a sub object of another object means (the objects may of course be composed of other objects)? – Benjamin Gruenbaum Apr 13 '14 at 20:34
  • Well, it means exactly what you said - you have two objects, one is inside the other. you can reach both by calling the big one - Obj1, Obj1.Obj2. Where are you going with that question? ;) – Reuven Karasik Apr 14 '14 at 08:41
  • @ReuvenKarasik I'll get there don't worry. Define "inside", what does it mean for an object to be inside another object? – Benjamin Gruenbaum Apr 14 '14 at 08:59
  • Oh, it means that Obj2 basically inherits everything that Obj1 has(the \_\_proto\_\_ thing, right?) - I'm not new to JS but only recently I was in a lecture about JS **objects**, so it's pretty new to me. I think that it also means that the object is reachable only through it's "father" (When I type `Computer` in the console nothing happends, but when I type `Person.Computer` I get the Computer object back) – Reuven Karasik Apr 14 '14 at 12:24
0

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.

Community
  • 1
  • 1
Mosho
  • 7,099
  • 3
  • 34
  • 51
0

// 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
0

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