supertrue
supertrue

Reputation: 2099

"Falsy or empty" in JavaScript: How to treat {} and [] as false

[] and {} are truthy in javascript.

But I want to treat them as false, and I would love to know how to do it in the least number of characters possible: without external libraries or separate functions, and small enough to fit comfortably between the parentheses of a conditional.

In other words:

What is the most concise way possible to check an unknown variable (that could be of any type) and return false if either of these apply: a) it's already falsy; or b) it's {} or []?

Upvotes: 1

Views: 3596

Answers (3)

Waterlink
Waterlink

Reputation: 2369

Given you have variable x to check:

!(!x || x.length === 0 || JSON.stringify(x) === '{}')

Here we go from the back, lets check if x is empty:

  1. it may be already falsy: !x
  2. it is Array and its length is zero: x.length === 0
  3. it is exactly {} object: JSON.stringify(x) === '{}'

Be careful with JSON.stringify method. It is available for majority of modern browsers, but not for IE6,7 if you need to support them. You should check at caniuse/JSON.

Any of this conditions being true will result in variable x to be empty.

So we need or's (||) and outer ! to invert result to check if x is not empty.

EDIT:

More concise way to do 3rd check would be due to @David remark (JSON.stringify will not work with object containing methods like {a: function(){ console.log("test"); }}):

(function (x){ for (key in x) { return 0; } return 1; })(x)

So resulting expression is:

!(!x || x.length === 0 || (function (x){ for (key in x) { return 0; } return 1; })(x))

EDIT:

If you wanna support only modern browser, than just use ecmascript 5 feature:

// Given x is defined
!(!x || typeof x === 'object' && !Object.keys(x).length)

Upvotes: 4

DJG
DJG

Reputation: 6543

If you know that the value is either an instance of Array or any other primitive (and not a general object,) than you can just put a + sign before the object and compare check if it's falsy, like so:

> +[] == false
true
> +[1,2] == false
false
> +[] == false
true
> +true == false
false
> +'hello' == false
false
> +25 == false
false
> +false == false
true

Dealing with a general instance of object is more complicated as you can see:

> +{} == false
false
> +{'a': 'b'} == false
false

Or if you know that the value in question is definitely not a primitive, and you are using ES 5, then you can use Object.keys to get all the keys of the object as an array and then the check becomes like this:

> +empty_obj == false || Object.keys(empty_obj) == false
true
> +obj == false || Object.keys(obj) == false
false

But if it is a primitive, you are in trouble:

> +true == false || Object.keys(true) == false
TypeError: Object.keys called on non-object
    at Function.keys (native)
    at repl:1:27
    at REPLServer.self.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:239:12)
    at Interface.EventEmitter.emit (events.js:95:17)
    at Interface._onLine (readline.js:202:10)
    at Interface._line (readline.js:531:8)
    at Interface._ttyWrite (readline.js:760:14)
    at ReadStream.onkeypress (readline.js:99:10)
    at ReadStream.EventEmitter.emit (events.js:98:17)

All in all, you can play around with the ||, && and == operators to try to find something short that will work in all cases all day, but I'm not sure that it's possible.

So, in summary, I would recommend just using a function like the one defined in @T.J. Crowder's answer.

Upvotes: 0

T.J. Crowder
T.J. Crowder

Reputation: 1074208

The most concise way with an array is !arr.length. Of course, that assumes you haven't added any non-index properties to the array (which is a perfectly valid thing to do). And as Qantas 94 Heavy points out in the comments, an array that's empty can have its length set to a non-zero value without gaining any actual entries.

Objects are more tricky. What's an "empty" object? One with no properties at all? One with no enumerable properties? What about properties from its prototype, if any?

If no enumerable properties does it for you, in ES5 you can use !Object.keys(obj).length. If you can't count on ES5, you have to have a loop:

var empty = true;
var key;
for (key in obj) {
    // Add an `obj.hasOwnProperty(key)` check here if you want to filter out prototype properties
    empty = false;
    break;
}

...which is obviously fairly ungainly.

You've said you don't want separate functions, but of course that's the best way to do this:

var isFalsey = (function() {
    var toString = Object.prototype.toString;

    function isFalsey(x) {
        var key;

        if (!x) {
            return true;
        }
        if (typeof x === "object") {
            if (toString.call(x) === "[object Array]") {
                return !x.length; // Assumes no non-element properties in the array
            }
            for (key in x) {
                // Add an `x.hasOwnProperty(key)` check here if you want to filter out prototype properties
                return false;
            }
            return true;
        }

        return false;
    }

    return isFalsey;
})();

Example with tests (source)

Upvotes: 4

Related Questions