46

I have two object literals like so:

var firstObject =
{
    x: 0,
    y: 1,
    z: 2,

    a: 10,
    b: 20,
    e: 30
}

var secondObject =
{
    x: 0,
    y: 1,
    z: 2,

    a: 10,
    c: 20,
    d: 30
}

I want to get the intersection of the keys these two object literals have like so:

var intersectionKeys  = ['x', 'y', 'z', 'a']

I can obviously do a loop and see if a key with the same name exists in the other object, but I am wondering if this would be a good case for some functional programming and map / filter / reduce usage? I myself have not done that much functional programming, but I have a feeling, that there could exist a clean and clever solution for this problem.

Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
Swiffy
  • 4,401
  • 2
  • 23
  • 49
  • 2
    Lodash has [intersection](https://lodash.com/docs#intersection) as a method, if you weren't already aware. – Xotic750 Dec 21 '15 at 10:01
  • @Xotic750 Seems to work only with arrays? Then again, there is probably many ways like `Object.keys` to obtain the keys as an array. – Swiffy Dec 21 '15 at 10:05
  • 4
    Sure, you will need the keys of each object, just like in the answers below, either [`Object.keys`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) or lodash has [`_.keys`](https://lodash.com/docs#keys). `_.intersection(_.keys(firstObject), _.keys(secondObject));` – Xotic750 Dec 21 '15 at 10:08

6 Answers6

41

A solution without indexOf.

var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
    secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };

function intersection(o1, o2) {
    return Object.keys(o1).concat(Object.keys(o2)).sort().reduce(function (r, a, i, aa) {
        if (i && aa[i - 1] === a) {
            r.push(a);
        }
        return r;
    }, []);
}

document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');

Second attempt with O(n).

var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
    secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };

function intersection(o1, o2) {
    return Object.keys(o1).filter({}.hasOwnProperty.bind(o2));
}

document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
28

The given answers are nice and astonishing but there could be a problem in void's answer and that is: "What if one of property values intentionally set to undefined."

Nina's answer is good (really fantastic) but as we are in era of fun JavaScript I think mine wont be too bad:

var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30 }
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 }

function intersect(o1, o2){
    return Object.keys(o1).filter(k => Object.hasOwn(o2, k))
}

console.log(intersect(a, b))

Update

onalbi mentioned some performance issue in comments which is rational and therefore the code bellow seems to be a better way to handle the problem:

var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30};
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30};

function intersect(o1, o2) {

  const [k1, k2] = [Object.keys(o1), Object.keys(o2)];
  const [first, next] = k1.length > k2.length ? [k2, o1] : [k1, o2];
  return first.filter(k => k in next);
}

console.log(intersect(a, b))
João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109
Morteza Tourani
  • 3,506
  • 5
  • 41
  • 48
  • 1
    Also here no body take care about the length of the object.. what happens if the object 'a' has length 1000 and object 'b' 10, is not better to filter if 10 index are set on 1000 than check 1000 are set in 10 ;).. – onalbi Aug 22 '18 at 20:08
  • 1
    Nice - it's also the fastest for me using this benchmark: https://jsben.ch/6dB51 – Jannis Hell Oct 15 '20 at 14:05
  • This should be the accepted answer. The first snippet is the simple and clean solution, the second optimal. – Peter Feb 06 '23 at 22:22
7

The procedure i will suggest is:

  1. Get the array of keys using Object.keys() for one of the objects.
  2. Find the intersection the array using .filter and checking if the second object contains a key matching the first array.

var firstObject = {
  x: 0,
  y: 1,
  z: 2,

  a: 10,
  b: 20,
  e: 30
}

var secondObject = {
  x: 0,
  y: 1,
  z: 2,

  a: 10,
  c: 20,
  d: 30
}

function getIntKeys(obj1, obj2){

    var k1 = Object.keys(obj1);
    return k1.filter(function(x){
        return obj2[x] !== undefined;
    });
  
}

alert(getIntKeys(firstObject, secondObject));
Monica Olejniczak
  • 1,146
  • 7
  • 9
void
  • 36,090
  • 8
  • 62
  • 107
  • 1
    I was probably wondering the same, like does it matter if you do `k1.filter` or `k2.filter` in any case? – Swiffy Dec 21 '15 at 10:01
  • @HunanRostomyan I don't believe it can ever be false, since the first value in the filter callback function returns the current value of the array being filtered. In this case, k1 is being filtered, making the first check redundant since we know the value has to exist as it is passed through the callback. – Monica Olejniczak Dec 21 '15 at 10:04
  • 1
    @Piwwoli Doesn't matter, the intersection of (A and B) and (B and A) is the same set, so you can begin with each member of A and check if it's also in B, *or* begin with each member of B and check if it's also in A. What I'm suggesting with my question, is that you don't need to check two memberships because the element (`x`) is already taken from one of those sets (@Monica just explained the same thing). – Hunan Rostomyan Dec 21 '15 at 10:07
  • @HunanRostomyan yes true, Update :) Thanks. – void Dec 21 '15 at 10:07
  • Do the changes made make this solution better than `O(N^3)`, as someone mentioned in Nina's answer? – Swiffy Dec 21 '15 at 10:09
  • Dude it is `O(N^2)`, and in this case with a cleaner code better then this complexity can not be achieved. – void Dec 21 '15 at 10:11
  • This seems wrong. If any of the properties have `undefined` values, then this won't work. Set `x:undefined` in `secondObject`. It will exude `x` from the final result. – adiga Jan 19 '19 at 11:04
4

Recursive function

This is other solution, maybe help you. I used a recursive function to intercept two objects. The advantage of this solution is that you not need worry about attributes that are objects at same time.

In this case the function intercept attributes that exist in both objects and asign the value of 'objSource' like final value of attribute intercepeted.

{
        function interceptObjects(objSource, objInterface) {
            let newObj = {};
            for (const key in objSource) {
                if (objInterface.hasOwnProperty(key)) {
                    // in javascript an array is a object too.
                    if (objSource[key] instanceof Object && !Array.isArray(objSource[key]) && objInterface[key] instanceof Object && !Array.isArray(objInterface[key])) {
                        newObj[key] = {};
                        newObj[key] = interceptObjects(objSource[key], objInterface[key])
                    } else {
                        newObj[key] = objSource[key];
                    }

                }
            }
            return newObj;
        }
        
        
        // FOR TESTING


    let objSource = {
            attr1: '',
            attr2: 2,
            attr3: [],
            attr4: {
                attr41: 'lol',
                attr42: 12,
                attr43: 15,
                attr45: [1, 4],
            },
            attr5: [2, 3, 4],
        };


        let objInterface = {
            attr1: null,
            attr4: {
                attr41: null,
                attr42: 12,
                attr45: [1],
            },
            attr5: [],
            attr6: null,
        };


        console.log(this.interceptObjects(objSource, objInterface));
    }
Amn
  • 531
  • 6
  • 9
3

Here is a simple entry, very functional, handles any number of objects, and returns the values of the matching keys from the first object passed.

This behavior is similar to that of array_intersect_key() in PHP in case anyone is searching for that.

function intersectKeys(first, ...rest) {
    const restKeys = rest.map(o => Object.keys(o));
    return Object.fromEntries(Object.entries(first).filter(entry => restKeys.every(rk => rk.includes(entry[0]))));
}

Expanded here for better explanation and commenting

function intersectKeys(first, ...rest) {
    // extract the keys of the other objects first so that won't be done again for each check
    const restKeys = rest.map(o => Object.keys(o));
    // In my version I am returning the first objects values under the intersect keys
    return Object.fromEntries(
        // extract [key, value] sets for each key and filter them, Object.fromEntries() reverses this back into an object of the remaining fields after the filter
        Object.entries(first).filter(
            // make sure each of the other object key sets includes the current key, or filter it out
            entry => restKeys.every(
                rk => rk.includes(entry[0])
            )
        )
    );
    // to get JUST the keys as OP requested the second line would simplify down to this
    return Object.keys(first).filter(key => restKeys.every(rk => rk.includes(key)));
}

It's important to note that this solution only works on string keys, Symbol keys will be ignored and the final object will not contain any. Though a similar function could be written to compare Symbol intersect as well.

kyrandita
  • 31
  • 1
  • 2
3

I know this is an old post, however, I want to share a solution I wrote today that I believe is efficient and clean.

function intersectingKeys(...objects) {
  return objects
    .map((object) => Object.keys(object))
    .sort((a, b) => a.length - b.length)
    .reduce((a, b) => a.filter((key) => b.includes(key)));
}

This function can take in n number of objects, and find the intersecting keys.

This is how it works.

  1. Map the objects, creating an array of key arrays.
  2. Sort the array by length, this puts the smallest key arrays first.
  3. Finally, reduce our key arrays, by filtering each list of keys against the next list.

I think the clever part of this algorithm is the pre sorting of the key arrays. By starting with the smallest list of keys, we have less work to do comparing keys.

Here is the usuage:

var firstObject = {
  x: 0,
  y: 1,
  z: 2,

  a: 10,
  b: 20,
  e: 30,
};

var secondObject = {
  x: 0,
  y: 1,
  z: 2,

  a: 10,
  c: 20,
  d: 30,
};

intersectingKeys(firstObject, secondObject);
// [ 'x', 'y', 'z', 'a' ]
Benjamin
  • 3,428
  • 1
  • 15
  • 25