6
\$\begingroup\$

lodash.isEmpty([{}]) returns false even though the object contained in the array is empty.

I need something that considers these kind of nested objects empty too and wrote the below recursive function. It looks a bit ugly and doesn't use any JavaScript specific tricks. Any comments welcome.

export function isDeepEmpty(input) {
  // catch null or undefined object
  if (!input) return true
  if (typeof(input) === 'object') {
    for (const entry of Object.values(input)) {
      if (!isDeepEmpty(entry)) {
        return false
      }
    }
    return true
  }
  // if input is not an object return false
  return false
}
\$\endgroup\$
9
  • \$\begingroup\$ You return true if any item is != true eg isDeepEmpty([0]) and which to me seams wrong, Could you be more specific in regards to what you mean by empty. \$\endgroup\$
    – Blindman67
    Jun 3, 2019 at 15:13
  • \$\begingroup\$ Good point, thanks very much! Let me think more. \$\endgroup\$
    – M3RS
    Jun 3, 2019 at 15:30
  • \$\begingroup\$ It appears lodash is an utility library, and this is where your isEmpty() function comes from. It tests for quite a lot of things. You seem to want to extend its capability. In that case I would expect you to use this isEmpty() function inside your isDeepEmpty() function. Why don't you? Please note that Code Review is only for code that is actually working. It says so in the How to Ask box, when you ask a question. \$\endgroup\$ Jun 3, 2019 at 16:24
  • \$\begingroup\$ @KIKOSoftware Your comment was very helpful, thanks! \$\endgroup\$
    – M3RS
    Jun 3, 2019 at 19:38
  • 2
    \$\begingroup\$ Please see What to do when someone answers. I have rolled back Rev 2 → 1 \$\endgroup\$ Jun 4, 2019 at 15:33

3 Answers 3

4
\$\begingroup\$

Some issues?

  • typeof is not a function. eg typeof(input) === 'object' is written typeof input === 'object'

  • null is an Object so when you test for an object using typeof you MUST make sure it is not null as well.

  • The test that you comment as // catch null or undefined object will catch any value that evaluates to a falsey, such as false, 0, ""

What is empty?

As it is unclear what you define as empty so I will make some assumptions, they are arbitrary and may or may not fit your needs.

  1. An array is empty if it contains only empty items.
  2. An object is empty if it is null or contains no own properties. If it has properties that are null or (defined as) undefined it is not empty.
  3. A string is empty if it contains no characters
  4. Empty items are null, undefined, {}, [], and ""

Examples

Empty

[], 
{}, 
[undefined],
[null],
[[], [], []], 
[{},,[],[[[null],[undefined]],[,,,,,]],
new Array(10),
null,
undefined,
""

Not empty

[0]
[{A:null}], 
{A:undefined}, 
[,,,0],
[[], [], [1]], 
[{},,[],[[[1],[]],[]],
(new Array(10))[1] = 0,
false,
true,
"  ",

Rewrite

With the above assumptions you can rewrite the code as a two functions.

As a non empty object mean we return false, and thus we do not need to iterate its values.

The entry point is isItemEmpty you would call it as you did isDeepEmpty

const isObjEmpty = obj => obj === null || Object.keys(obj).length === 0;
const isItemEmpty = item => item === undefined || item === "" ||
    (Array.isArray(item) && item.every(isItemEmpty)) ||
    (typeof item === "object" && isObjEmpty(item));

Usage

isItemEmpty([{},[],[[]]]);    // returns true
isItemEmpty([{A:0},[],[[]]]); // returns false
isItemEmpty("");              // returns true
\$\endgroup\$
1
\$\begingroup\$

This is my improved version.

  • it builds on top of isEmpty()
  • correctly treats undefined in my test cases (tricky as undefined is not an object unlike null)
  • isDeepEmpty([0]) = true and return values for other test cases are pretty intuitive
  • isDeepEmpty(42) = false, but isEmpty(42) behaves the same way

Here:

import isEmpty from 'lodash/fp/isEmpty'

export function isDeepEmpty(input) {
  if(isEmpty(input)) {
    return true
  }
  if(typeof input === 'object') {
    for(const item of Object.values(input)) {
      // if item is not undefined and is a primitive, return false
      // otherwise dig deeper
      if((item !== undefined && typeof item !== 'object') || !isDeepEmpty(item)) {
        return false
      }
    }
    return true
  }
  return isEmpty(input)
}
\$\endgroup\$
1
\$\begingroup\$

My review

  • the presented code is not generic enough to handle what can be considered as an empty value;
  • the check if (!input) return true is not robust for all possibile use cases of what can be considered as null, undefined, or empty;

Alternative solution

This version, differently that last proposed solution, it avoids to use lodash library, thus recursively calling an empty function (inspired to the PHP empty), that supports as input a empty values list emptyValues, in order to explicitly define what is empty, like [undefined, null, ''] or [undefined, null, '', 0]:

function emptyDeep(mixedVar, emptyValues = [undefined, null, '']) {
    var key, i, len
    for (i = 0, len = emptyValues.length; i < len; i++) {
        if (mixedVar === emptyValues[i]) {
            return true
        }
    }
    if (typeof mixedVar === 'object') {
        for (const item of Object.values(mixedVar)) {
            if (!emptyDeep(item, emptyValues)) {
                return false
            }
        }
        return true
    }
    return false
}

Examples:

emptyDeep([{},[],[[]]]);
true
emptyDeep([{A:0},[],[[]]])
false
emptyDeep([{A:0},[],[[]]],  [undefined, null, '', 0])
true
emptyDeep({x:[{},[],[[]]]})
true
emptyDeep(0, [undefined, null, '']);
false
emptyDeep(0, [undefined, null, '', 0])
true
\$\endgroup\$
1
  • 1
    \$\begingroup\$ You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. \$\endgroup\$ May 27, 2021 at 8:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.