-1

I'm looking for a method to deeply determine whether or not an object consists of nothing but empty objects.

For example, that method should return true for each one of the following objects:

const obj1 = {};

const obj2 = {
    a: {}
    b: {}
    c: {}
};

const obj3 = {
    a: {
        a1: {}
    },
    b: {
        b1: {},
        b2: {}
    },
    c: {
        c1: {},
        c2: {},
        c3: {}
    }
};

And it should return false for the following object:

const obj4 = {
    a: {
        a1: {}
    },
    b: {
        b1: {},
        b2: {}
    },
    c: {
        c1: {},
        c2: {},
        c3: {
            x: 5
        }
    }
};

The simplest method I came up with is:

function isEmpty(object) {
    return typeof object === 'object' &&
           Object.keys(object).every(key => isEmpty(object[key]));
}

I couldn't find any counterexamples, so I'd like to ask if there are any flaws in this method.

halfer
  • 19,824
  • 17
  • 99
  • 186
goodvibration
  • 5,980
  • 4
  • 28
  • 61
  • 2
    I'd just like to point out that technically only the `obj1` is truly empty. The rest of the examples do not contain **only** empty objects. For example, the `obj3` object contains three properties that are *not* empty objects. They do have properties inside them. Nonetheless, I know what you mean. Just wanted to point out possible ambiguities ;) – Sebastian Kaczmarek Dec 28 '20 at 19:14
  • 1
    @SebastianKaczmarek: That's why I explicitly wrote `whether or not an object consists of nothing but empty objects` (both in the title of the question and in the opening statement). – goodvibration Dec 28 '20 at 19:15
  • Would this be a good candidate for one of the SE code review sites? – SherylHohman Dec 28 '20 at 19:39
  • Note that we prefer a technical style of writing here. We gently discourage greetings, hope-you-can-helps, thanks, advance thanks, notes of appreciation, regards, kind regards, signatures, please-can-you-helps, chatty material and abbreviated txtspk, pleading, how long you've been stuck, voting advice, meta commentary, etc. Just explain your problem, and show what you've tried, what you expected, and what actually happened. – halfer Dec 28 '20 at 22:22

2 Answers2

1

Indeed, you can use a recursive function. Here are a few changes you could consider:

  • Make sure to address null correctly (its typeof is "object"!)
  • Use Object.values instead of Object.keys, so that...
  • You can pass the function reference as-is to the every callback.
  • You can make it an arrow function, using the expression syntax
const isDeepEmpty = obj => Object(obj) === obj &&  
                           Object.values(obj).every(isDeepEmpty);
trincot
  • 317,000
  • 35
  • 244
  • 286
  • `Object(obj) === obj` is equivalent to `typeof object === 'object'` with the exception of `null`? – goodvibration Dec 28 '20 at 19:18
  • Indeed, it is like you say. – trincot Dec 28 '20 at 19:18
  • Switching from `keys()` to `values()` is a neat idea! – goodvibration Dec 28 '20 at 19:20
  • Same goes for "pass the function reference as-is" (which I guess can be applied only after switching from `keys()` to `values()`. – goodvibration Dec 28 '20 at 19:21
  • 1
    Ah, I see you just added that point in a bullet. – goodvibration Dec 28 '20 at 19:21
  • How bad a performance impact is `Object(obj) === obj`? – goodvibration Dec 28 '20 at 19:24
  • I'm looking into [Object comparison in JavaScript](https://stackoverflow.com/questions/1068834/object-comparison-in-javascript), and I don't even see that as a suggested method. – goodvibration Dec 28 '20 at 19:28
  • Why not measure the performance? If your object is such that you would have hundreds of thousands of calls, then it might matter, and you might be better of with a separate check for `null` (with `typeof obj === "object" && obj`). But for regular use, there should not be a noticeable difference. I always go for that in my code. – trincot Dec 28 '20 at 19:28
  • So how come it is not suggested in any of the answers to the question that I linked above? – goodvibration Dec 28 '20 at 19:29
  • Because it is locked and does not accept answers? But also because it asks a different question... The question is really ["how to check if a value is an object"](https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript). It is suggested in the answer that got the bounty awarded. – trincot Dec 28 '20 at 19:29
  • I have searched for `Object(` conclusively in that post and couldn't find any. Also, that question says "What is the best way to compare objects in JavaScript?". Thank you very much, BTW. – goodvibration Dec 28 '20 at 19:42
  • You didn't follow my link then :) Here is a direct link to the answer in [Check if a value is an object in JavaScript](https://stackoverflow.com/a/14706877/5459839) – trincot Dec 28 '20 at 19:46
  • 1
    Oh, you meant, that's the question that **I should have** been looking at. Got it! Thank you very much again :) – goodvibration Dec 28 '20 at 19:54
  • If we're listing flaws, and using things are aren't in the example (like null), then we should also point out that _circular references_ break it. A JavaScript object can contain a property that refers to itself. E.g.: `ob1j.self = obj1` would be quicksand for your recursive function. – Wyck Dec 28 '20 at 20:48
0

Anything that's null will cause a TypeError, because null is type 'object', but it has no keys() method.

const obj2 = {
  a: {},
  b: {},
  c: {
    d: null
  }
};

function isEmpty(object) {
  return typeof object === 'object' &&
    Object.keys(object).every(key => isEmpty(object[key]));
}

console.log(isEmpty(obj2));
// TypeError: null is not an object (evaluating 'Object.keys(object)')
terrymorse
  • 6,771
  • 1
  • 21
  • 27