Null Conditional Operators

Javascript

Javascript Problem Overview


C# 6.0 has just been released and has a new nice little feature that I'd really like to use in JavaScript. They're called Null-conditional operators. These use a ?. or ?[] syntax.

What these do is essentially allow you to check that the object you've got isn't null, before trying to access a property. If the object is null, then you'll get null as the result of your property access instead.

int? length = customers?.Length;

So here int can be null, and will take that value if customers is null. What is even better is that you can chain these:

int? length = customers?.orders?.Length;

I don't believe we can do this in JavaScript, but I'm wondering what's the neatest way of doing something similar. Generally I find chaining if blocks difficult to read:

var length = null;
if(customers && customers.orders) {
    length = customers.orders.length;
}

Javascript Solutions


Solution 1 - Javascript

Called "optional chaining", it's currently a [TC39 proposal in Stage 4][2]. A [Babel plugin][3] however is already available in v7.

Example usage:

const obj = {
  foo: {
    bar: {
      baz: 42,
    },
  },
};

const baz = obj?.foo?.bar?.baz; // 42

const safe = obj?.qux?.baz; // undefined

[2]: https://github.com/tc39/proposal-optional-chaining "TC39 proposal in Stage 3" [3]: https://github.com/babel/babel/releases/tag/v7.0.0-alpha.15 "Babel plugin"

Solution 2 - Javascript

Js logical operators return not true or false, but truly or falsy value itself. For example in expression x && y, if x is falsy, then it will be returned, otherwise y will be returned. So the truth table for operator is correct.

In your case you could use expression customers && customers.orders && customers.orders.Length to get length value or the first falsy one.

Also you can do some magic like ((customers || {}).orders || {}).length (Personally, I don't like this syntax and possible garbage collection pressure as well)

Or even use maybe monad.

function Option(value) {
    this.value = value;
    this.hasValue = !!value;
}

Option.prototype.map = function(s) {
    return this.hasValue
        ? new Option(this.value[s])
        : this;
}

Option.prototype.valueOrNull = function() {
    return this.hasValue ? this.value : null;
}

var length = 
    new Option(customers)
        .map("orders")
        .map("length")
        .valueOrNull();

It's longer than all the previous approaches, but clearly shows your intentions without any magic behind.

Solution 3 - Javascript

There are several ways to improve code readability (depending on your needs):

  1. You already use [tag:BabelJS] (v7 or above) and you use "Optional Chaining" babel plugin (finished proposal) (or just preset-stage-3):

     const length = customers?.orders?.Length;
    
     // With default value (otherwise it will be `undefined`):
     const length = customers?.orders?.Length || defaultLength;
    
     // File: .babelrc
     { "plugins": ["@babel/plugin-proposal-optional-chaining"] }
    
  2. You already use [tag:lodash] (v3.7 or greater): Use the lodash.get method:

     var length = _.get(customers, 'orders.length');
    
     // With default value (otherwise it will be `undefined`):
     var length = _.get(customers, 'orders.length', defaultLength);
    
  3. Plain javascript:

     var length = customers && customers.orders && customers.orders.length;
    
     // With default value (otherwise it may be whatever falsy value "customers" or "customers.orders" might have):
     var length = (customers
         && customers.orders
         && customers.orders.length) || defaultLength;
    

Solution 4 - Javascript

Here's a quick and dirty version that works.

String.prototype.nullSafe = function() {
    return eval('var x='+this.replace(/\?/g,';if(x)x=x')+';x');
};

Example usage:

var obj = { 
    Jim: 1,
    Bob: { "1": "B", "2": "o", "3": "b" },
    Joe: [ 1, 2, 3, { a: 20 } ]
};

 obj.Joe[3].a                 // 20    
"obj.Joe[3]?.a".nullSafe()    // 20

 obj.Joe[4].a                 // Error: Can't read 'a' from undefined
"obj.Joe[4].a".nullSafe()     // Error: Can't read 'a' from undefined
"obj.Joe[4]?.a".nullSafe()    // undefined

 obj.Jack[3].a.b              // Error: Can't read '3' from undefined
"obj.Jack[3].a.b".nullSafe()  // Error: Can't read '3' from undefined
"obj.Jack?[3].a.b".nullSafe() // undefined

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIanView Question on Stackoverflow
Solution 1 - JavascriptBrent LView Answer on Stackoverflow
Solution 2 - JavascriptUładzisłaŭView Answer on Stackoverflow
Solution 3 - JavascriptMariano DesanzeView Answer on Stackoverflow
Solution 4 - JavascriptJ Bryan PriceView Answer on Stackoverflow