Deleting object properties if they are not found in another object. (javascript) Let's take these two objects as an exemple:

let objA = {
    key1 : "whatever",
    key2 : "whatever",
    key3 : "whatever",
    key4 : "whatever",
    key5 : {
        subKey1: "whatever",
        subKey2: "whatever"
    }
}
let objB = {
    key1 : "whatever",
    key2 : "whatever",
    key3 : "whatever",
    key5 : {
        subKey1: "whatever"
    }
}


I want to delete all the properties of objA if they are not present in objB. The values of the properties do not matter, only the keys themselves. The challenge, of course, is for it to work with any object, no matter how deeply nested they can be.

I came up with this solution based on this answer to a similar question.



let objA = {
    key1 : "whatever",
    key2 : "whatever",
    key3 : "whatever",
    key4 : "whatever",
    key5 : {
        subKey1: "whatever",
        subKey2: "whatever"
    }
}
let objB = {
    key1 : "whatever",
    key2 : "whatever",
    key3 : "whatever",
    key5 : {
        subKey1: "whatever"
    }
}

const deepSameKeys = (o1, o2) =&gt; {
    const o1keys = Object.keys(o1).sort();

    return o1keys.every(key =&gt; {
        const v1 = o1[key];
        const v2 = o2[key];

        let goDeeper = true;
        if (o2[key] == undefined) {
            console.log('@found a key to delete: ', key);
            delete o1[key];
            goDeeper = false;
        }
        const t1 = typeof v1;
        return t1 === "object" &amp;&amp; goDeeper ? deepSameKeys(v1, v2) : true;
    });
};

let result = deepSameKeys(objA, objB)
console.log(result)
console.log(objA) // no more key4 nor subKey2




So far so good, but I wonder if someone can come up with something better or   a situation where my solution would fail.
Bugs and Problems

There are a few problems with your code


Crash when o1 references null. 

Reason typeof null === "object" is true. 

null is an object when inspected with typeof however Object.keys(null) will throw the error "Can not convert null to Object" so if you have any properties that are null your code will throw an error.
Crash when both objects have similar cyclic references. 

Many objects have references to themselves (are cyclic). Your code does not check if there is a cyclic reference and will continue to recurse until the call stack overflows (which is a thrown error)


General

Always aim to keep the code complexity as low as possible.


The variable goDeeper is not needed. You can return true where you set it to false
I do not see why you would need the keys sorted. Sorts are expensive operations, you should always ask yourself "Do I need to sort?" before using it.
The function deepSameKeys will always return true so why return a value at all?
It is unclear if you want to remove undefined properties, or all properties that evaluate to undefined. As you have it you remove all that evaluate to undefined.

I.E. the object {a: undefined} contains a property named a while {} does not contain the property a yet both will evaluate a as undefined.

You can use the in operator which will eval to true for the first object and false for the second. Note this also checks the prototype chain so you may want to use Object.hasOwnProperty if this will be an issue
The naming is very poor, not indecipherable but could be a lot better.
When using recursive functions you should try to avoid using iterators that use anonymous functions EG Array.every

Most recursive functions are just an alternative way to implement a stack to hold the current item's state as you search deeper. 

This state is held in the function context (variables scoped to the function) 
and stored on the JS heap.

Defining a function inside that context means that a second copy of the state is created as a closure, and also another context for the anonymous function.

The additional state per item is a lot of unneeded overhead (and halves the max depth of recursion).

If you use a for, while, or do loop you avoid the overhead .


Solutions

Bug 1 the solution is to change the lines...

 const t1 = typeof v1;
 return t1 === "object" &amp;&amp; goDeeper ? deepSameKeys(v1, v2) : true;


...to include a check for null and have the variable store a bool.

 const isObject = v1 !== null &amp;&amp; typeof v1 === "object";
 return isObject ? deepSameKeys(v1, v2) : true;


Note I have removed goDeeper

Bug 2 There are a few options here. 


If the objects are parsed directly from JSON files then they can not be cyclic and the is no danger of error. This can also be an unenforced condition of the call (no cyclic object) and let the caller deal with the error is the breach the condition.
Limit the recursion depth. JavaScript's Array.map function uses this method to protect against cyclic references and simply counts the depth down per call and the up on exit. It will not go deeper is this count is &lt;= 0
Track objects recursed using a Set. On each call check if the current object is in the Set, if it is return, if not then put it into the set and process the object. 


Rewrite

I am assuming you only want to delete missing properties not properties referencing undefined and the return is not needed.

There are a few versions. After the first I have reduced the variable name lengths to keep the code compact for CR snippet container.


Delete undefined properties in reference from dirty. DOES not remove if property references undefined.
Function returns undefined
Using Object.entries to get at the key and value at the same time.




Keeping the same (almost) behavior and cyclic bug.

const removeMissingProps = (dirty, reference) =&gt; {
    Object.entries(dirty).forEach(([key, val]) =&gt; {
        if (key in reference) {
            val !== null &amp;&amp; typeof val === "object" &amp;&amp; removeMissingProps(val, reference[key]);
        } else { delete dirty[key] }
        return true;
    });
};




To provide a deeper call stack and avoid double state capture.  Using for loop.

function remProps(dirty, ref) {
    for (const [key, val] of Object.entries(dirty)) {
        if (key in ref) {
            val !== null &amp;&amp; typeof val === "object" &amp;&amp; remProps(val, ref[key]);
        } else { delete dirty[key] }
    }
};




To protect against call stack overflow by depth count

function remProps(dirty, ref, depth = 10) {
    if (depth &gt; 0) {
        for (const [k, v] of Object.entries(dirty)) {
            if (k in ref) {
                v !== null &amp;&amp; typeof v === "object" &amp;&amp; remProps(v, ref[k], depth - 1);
            } else { delete dirty[k] }
        }
    }
};




Uses a WeakSet to check for repeated references.

function remProps(dirty, ref) {
    const checked = new WeakSet();
    remProps(dirty, ref);
    function remProps(dirty, ref) { 
        if (!checked.has(dirty)) {
            checked.add(dirty);
            for (const [k, v] of Object.entries(dirty)) {
                if (k in ref) {                    
                    v !== null &amp;&amp; typeof v === "object" &amp;&amp; remProps(v, ref[k]);
                } else { delete dirty[k] }
            }
        }
    };
};




Update I forgot to add the last one.

Almost all the recursion functions I see are just implementations of stacks. Recursion has two drawbacks, small stack size (limited to the size of the call stack) and the needless capture of unrelated state.

The following example uses an array rather than the call stack. Its behavior is slightly different (more intuitive) from the function above if you have cyclic references.

There can be significant performance gains using stacks.

function remProps(dirty, ref) {
    const push = (v, ref) =&gt; !chk.has(v) &amp;&amp; (chk.add(v), stack.push([v, ref]));
    const stack = [[dirty, ref]], chk = new WeakSet([dirty]);     
    while (stack.length) {
        [dirty, ref] = stack.pop();
        for (const [k, v] of Object.entries(dirty)) {
            k in ref ? 
                v !== null &amp;&amp; typeof v === "object" &amp;&amp; push(v, ref[k]):
                delete dirty[k];
        }
    }
}