Concepts of programming languagesJavascriptTypeScript

Elvis Operator (aka Safe Navigation) in JavaScript and TypeScript


One of the first feature requests of TypeScript was the Elvis operator. More precisely, the null-safe navigation operator. That’s a variant of the “dot” operator allowing you to access attributes and methods even if the object preceding the dot is null or undefined. In most languages implementing the Elvis operator, the expression null?.firstName simply returns null, relieving the developers of guarding the attribute accesses with countless if statements. Languages like JavaScript are even worse than the average programming languages. Not only can an attribute be null, it can also be undefined, which is sort of even more astral than null itself.

Obviously, the Elvis operator is very useful. It’s been an important part of the success story of languages like Groovy and Kotlin. It’s even a part of the Angular HTML template language.

But it hasn’t been added to the TypeScript language, and for a good reason. TypeScript is meant to be a super-set of JavaScript, plus a glimpse of the future. So the language designers can only add features that are sure to be part of a future ECMAScript version. They can also add features that’ll never make it into the JavaScript language. The nightmare of the TypeScript language designers is to come up with a feature that’s adopted by ECMAScript later – but slightly different.

Be that as it may, JavaScript is such an incredibly flexible language that there are several solutions. None of these solutions reach the beauty and simplicity of the Elvis operator, but they are close enough to render programmers of many other languages, such as Java, flabbergasted.

Classical approaches

When I was new to JavaScript, I went the hard way and added two checks to each and every navigation:

if ((typeof person !== "undefined") && (person !== null)) {
   person.greet();
}

This approach has several disadvantages. First, it’s far from being the intuitive solution. Second, it’s hardly practical if you’re navigating several levels, such as person.oldestDaughter.drivingLicense.issueDate. Even assuming the person exists, we still have to check they have a daughter, and the daughter, in turn, has a driving license. That’s six checks at least. There’s got to be a better way!

Bang bang!

Luckily, JavaScript allows you to use a very relaxed syntax. In most cases, you can exclude both being undefined and being null like so:

if (person) {
   person.greet();
}

This approach almost always works. There are a few corner cases with slightly confusing semantics, so I recommend using the “bang, bang, you’re a boolean!”-operator by default:

if (!!person) {
   person.greet();
}

As far as I know, none of these corner cases affects the if statement, but I still recommend using the “bang bang” operator by default. First, it’s an optical signal (“this value’s gonna have a useful value!”). Second, using it by default means you won’t forget it when it really matters.

If you’re concerned about our CO2 footprint or (more mundane) the performance of the application: modern JavaScript engines are extremely sophisticated beasts. They are surprisingly efficient. I didn’t inspect the resulting machine code yet, but the “bang bang” operator is so abundant it’s most likely already covered by the JIT compiler as a special case.

Our data structure

Before continuing, let’s introduce a little data structure we can experiment with, so I commit these lines in the examples below:

const jane = {
  name: 'Jane',
  age: 35
};

const john = {
  name: 'John',
  address: {
    street: '7th Avenue',
    city: 'Ecmaville',
    zipCode: '23233'
  },
  sister: jane,
  age: 28
};

… or a default

“Bang bang” is great, but it doesn’t get us nowhere if we need to traverse the object hierarchy several levels. In such a situation, a common solution is to use default values like so:

console.log(((john.sister || {}).address || {}).street);

This approach works because the || operator return the second parameter if the first parameter is falsy. It isn’t restricted to booleans. It operates an arbitrary data types.

Thing is, it’s not always obvious whether an object is falsy or not. Empty strings and the number zero are falsy, too. Most of the time that’s precisely what you want, but be warned: the semantics are tricky, so proceed with care.

That said, using default values is a great way to go. It’s a bit clumsy in TypeScript because you have to add all those type information snippets:

// JavaScript version:
console.log(((john.sister || {}).address || {}).street);

// TypeScript version:
console.log(((john.sister || ({} as Person)).address || ({} as Address)).street);

Default values are great, but you’ve already seen the problem with them: they are most valuable if you navigate only a single level. That’s the most common use-case. Nonetheless, navigating several levels as in the example above is fairly common, too. So let’s have a look if there’s a better solution.

Using the Proxy object

Now for some fancy stuff. If you’ve got the liberty to use ECMAScript 2015, you can wrap a Proxy around your JavaScript object. This Proxy object gives you access to a dozen low-level methods. For instance, you can use the get() method to override the navigation operator (i.e. the dot between the names of the attributes):

function safe(obj) {
  return new Proxy(obj, {
    get: function(target, name) {
      const result = target[name];
      if (!!result) {
        return (result instanceof Object)? safe(result) : result;
      }
      return safe({});
    }
  });
}

The method get is called when the JavaScript accesses a method or a function of an object. So now we can write code like this:

var sjohn = safe(john);                    
console.log(sjohn.name);                  // --> "John"
console.log(sjohn.address.street);        // --> "7th Avenue"
console.log(sjohn.sister.name);           // --> "Jane"
console.log(sjohn.sister.address);        // --> {}
console.log(sjohn.sister.address.street); // --> {}

There are more elaborate versions of this approach out there, such as this gist on GitHub. In particular, I simply returned an empty object as the default value. This way you can chain the dot operators without further ado. In a real-world application, you’d prefer to get something like undefined.

Talking about the real world: I don’t think you’ll find this approach frequently in the wild. One of the reasons is that Internet Explorer 11 doesn’t support Proxies, and by the look of it, it never will. Nonetheless, once again I’m astonished about the flexibility of the JavaScript language. Being able to tweak attribute accesses and inheritance in a more-or-less safe way is a feature few programming languages have.

What about Babel?

When it comes to support older browsers, Babel is a common answer. Proxies are no exception. There’s a Babel plugin allowing you to use Proxies in ECMAScript 5. I haven’t tried it myself, but it looks promising. Check it out!

Talking of Babel: Babel 7.0 is going to support the Elvis operator. At the time of writing, it hasn’t been released yet. I suppose using Babel is one of the most interesting options because it can generate very efficient code.

TypeScript: Asking the compiler for help

If you’re using TypeScript, please configure the tsconfig.json, setting the TypeScript compiler to a very strict mode. My favorite setting is

"strictNullChecks": true

This setting removes null and undefined from the set of legal values of any variable. In other words: every variable you define is guaranteed to never be null. You don’t have to guard your navigations with null checks. That’s the job of the compiler. That, in turn, is the way it should be.

Of course, sometimes you need variables with null values. Usually, that’s because you’re working with a library that doesn’t know about TypeScript. At other times, null is simply a useful default value. TypeScript supports this use case with union types:

private daughter: Person;
private son:      Person | null;

Now TypeScript assumes that you always have a daughter, so you don’t need a null check. However, it knows you may or may not have a son. So it checks every access of an attribute or a method of the son attribute. If you forget to add the null, TypeScript faithfully reminds you.

Lodash’s get function

The internet has countless descriptions how to write your own safe navigation: Just write a get function and pass the path as string to it. The popular lodash library already offers such a get function, so I recommend using it instead:

import * as _ from 'lodash'; // or var get = require('lodash.get');
console.log(_.get(john, 'name'));                              // --> "John"
console.log(_.get(john, 'address.street'));                    // --> "7th Avenue"
console.log(_.get(john, 'sister.name'));                       // --> "Jane"
console.log(_.get(john, 'sister.address'));                    // --> "undefined"
console.log(_.get(john, 'sister.address.street', 'unknown'));  // --> "unknown"

Future ECMAScript

I’ve tried to find out whether a future ECMAScript version is going to include the Elvis operator. The only reference I found indicates that they’ve discussed the feature, considered it useful, but postponed it to the future because the semantics are unclear. Last year the proposal status has been raised to level 1, which means the proposal is taken serious now. This, in turn, encourage the Babel team to add the feature (now called “null propagation operator” or “optional chaining operator”) to the yet-to-be-released Babel 7.0.

If you’ve got more information, please drop a comment!

Wrapping it up

As annoying the lack of the Elvis operator is, as surprising it is to see how many workarounds there are to overcome this limitation. I suppose the most common approaches in the wild are the default value (using || {}) and the Lodash get function. However, the latter probably affects the performance of your application because it uses strings, so the compiler has a hard time compiling and optimizing the application.


Dig deeper

Null Propagation Operator

Gidi Meir Morris on using Proxies to simulate the Elvis operator
a gist on GitHub using Proxies to achieve safe navigation
StackOverflow discussion on Proxy polyfill in IE11
Documentation of Proxy.handler.get()
Documentation of the Proxy object in ECMAScript 2015
Babel plugin emulating Proxies for older browsers