/**
 * @param {any} o1
 * @param {any} o2
 * @return {boolean}
 */
var areDeeplyEqual = function(o1, o2) {
    // passes strict equality
    if (o1 === o2) {
        return true;
    }

    // one is ‹null›
    if (o1 === null || o2 === null) {
        return false;
    }

    // different string representations
    if (String(o1) != String(o2)) {
        return false;
    }

    // if it's not an object we can decide with strict equality
    if (typeof o1 !== 'object') {
        return o1 === o2;
    }

    if (Array.isArray(o1)) {
        return (
            o1.length === o2.length
            && Array.from(o1.keys()).every(i => areDeeplyEqual(o1[i], o2[i]))
        );
    }

    return (
        Object.keys(o1).length === Object.keys(o2).length
        && Object.keys(o1).every(key => areDeeplyEqual(o1[key], o2[key]))
    );
};