Wednesday, February 12, 2025

Testing for Empty Inherited JavaScript Object Properties

Testing for Empty Inherited JavaScript Object Properties

Testing for emptiness in JavaScript, that is to say, when a variable has been declared but not assigned a value, is easy enough with primitive types like numbers and strings because of loose typing. That’s what lets you perform tests like if (variable) doSomething(); which rely on the application of truthiness and falsiness. Objects are a whole other matter though. While JS libraries like jQuery only consider {} to represent a truly empty object, there are developers who would rather treat them more liberally so that an object may have attributes, but must themselves be falsy in order for the object to be empty. I dealt with the subject to some extent in my Testing For Variable Emptiness In JavaScript article. Today I’d like to expand on that foundation and throw in a couple of my own contributions into the ring. I hope that they benefit you in your coding endeavors.

Checking an Object’s Properties

Most people tend to associate the for in loop with object property iteration. A more functional alternative is to use the static Object.keys() method. It returns an array of properties that you can iterate over or apply a function to such as every(), map(), or some().

console.log(Object.keys({
  prop1: 'a string',
  prop2: 0,
  prop3: {},
  prop4: [1,2,3],
  prop5: {
    prop1a: 'another string',
    prop2a: 0
  }
}));

//outputs prop1,prop2,prop3,prop4,prop5

I particularly like some() because it stops iterating as soon as the callback function returns true. Within the context of checking for an object’s emptiness, we can exit as soon as we find a property which is falsy. There is also an element of recursion because an object may contain other objects as data members. With that idea in mind, here’s a function that invokes the Array.some() function on the results of Object.keys().

function isEmpty(obj) {
  return !(obj
       && Object.keys(obj).some(function(key, index) {
               return (  typeof this[key] === 'object' && !isEmpty(this[key])) 
                      || typeof this[key] === 'function' 
                      ||        this[key]  ==  true;
          }, obj));
}

The above function exits as soon as an object’s properties or child object’s properties evaluate to a truthy value. Since we want to return true if the object is empty, we have to apply the bang (!) operator to the final results to reverse them (true becomes false and vice versa).

Dealing with Functions

A function is a distinct type in JavaScript that can be treated just like any other data type. For instance, you can store them in variables, as well as pass them to and/or return them from other functions. In the above function, we treated the existence of any function as a non-empty condition. The truth is, a function may be empty just like any other data type. While empty functions are not exactly ubiquitous, they are quite easily created. Just as an object or array may be empty using curly {} braces and square [] brackets respectively, an empty function may be declared as follows:

var myEmptyFunction = function() {
  //I don't do anything!
};

The key to identifying an empty function is the absence of any functional code inside the body. I say functional because developers often include some TODO-type comments for later completion. This simple function removes all one line comments from the function body so that it may be tested for content.

Function.prototype.getBody = function() {
    // Get content between first { and last }
    var body = this.toString().match(/{([sS]*)}/m)[1];
    // Strip 1 line comments (//)
    return body.replace(/^s*//.*$/mg,'');
};

If only it were so easy to remove block comments. Unfortunately, the characters // or /* can also appear inside strings, regular expressions and what have you. There are JavaScript libraries that do a pretty impressive job at removing most block comments form code, but a JavaScript-based solution can only do so much. If you’re interested in pursuing this avenue further, I found an excellent comments stripper that you can try here.

Here is the isEmpty() function again, modified to incorporate Function.getBody(). A regular expression tests for any non-whitespace characters in the return string to rule out an empty function.

function isEmpty(obj) {
  return !obj
      || !Object.keys(obj).some(function(key, index) {
               return (  typeof this[key] === 'object'   && !isEmpty(this[key])) 
                      || (typeof obj[key] === 'function' && /S+/.test(obj[key].getBody()))
                      ||        this[key]  ==  true;
          }, obj);
}

Checking Ancestor Properties

For in loops only access object properties that are enumerable. Object members such as toString() are not included, although inherited properties are. What this means is that if you have an object called Foo and it has a property named bar, it will be included in the for in loop of a child object built on Foo’s prototype. You can choose to either include or ignore inherited properties in your test using the Object.hasOwnProperty() method.

function isEmpty(obj, checkAncestorProps) {
  if(obj !== undefined && obj !== null) {
    if (obj.length > 0) return false;

    for (var key in obj) {
        if ((checkAncestorProps || hasOwnProperty.call(obj, key)) 
            && (typeof obj[key] === 'object'    && !isEmpty(obj[key])) 
            || (typeof obj[key] === 'function'  && /S+/.test(obj[key].getBody()))
            ||         obj[key]  ==  true)
               return false;
    }
  }
  return true;    
}

Conclusion

As you can see, you can test for just about any kind of variable emptiness imaginable so long as you have a clear definition of what constitutes empty for your purposes. Depending on the task at hand, you may not have to cover every conceivable data type either. The important thing is to be consistent in your application of emptiness criteria.

Rob Gravelle
Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured