Is there a null-coalescing (Elvis) operator or safe navigation operator in javascript?
JavascriptJqueryGroovySafe Navigation-OperatorJavascript Problem Overview
I'll explain by example:
Elvis Operator (?: )
> The "Elvis operator" is a shortening > of Java's ternary operator. One > instance of where this is handy is for > returning a 'sensible default' value > if an expression resolves to false or > null. A simple example might look like > this:
def gender = user.male ? "male" : "female" //traditional ternary operator usage
def displayName = user.name ?: "Anonymous" //more compact Elvis operator
> Safe Navigation Operator (?.) > > The Safe Navigation operator is used > to avoid a NullPointerException. > Typically when you have a reference to > an object you might need to verify > that it is not null before accessing > methods or properties of the object. > To avoid this, the safe navigation > operator will simply return null > instead of throwing an exception, like > so:
def user = User.find( "admin" ) //this might be null if 'admin' does not exist
def streetName = user?.address?.street //streetName will be null if user or user.address is null - no NPE thrown
Javascript Solutions
Solution 1 - Javascript
You can use the logical 'OR' operator in place of the Elvis operator:
For example displayname = user.name || "Anonymous"
.
But Javascript currently doesn't have the other functionality. I'd recommend looking at CoffeeScript if you want an alternative syntax. It has some shorthand that is similar to what you are looking for.
For example The Existential Operator
zip = lottery.drawWinner?().address?.zipcode
Function shortcuts
()-> // equivalent to function(){}
Sexy function calling
func 'arg1','arg2' // equivalent to func('arg1','arg2')
There is also multiline comments and classes. Obviously you have to compile this to javascript or insert into the page as <script type='text/coffeescript>'
but it adds a lot of functionality :) . Using <script type='text/coffeescript'>
is really only intended for development and not production.
Solution 2 - Javascript
I think the following is equivalent to the safe navigation operator, although a bit longer:
var streetName = user && user.address && user.address.street;
streetName
will then be either the value of user.address.street
or undefined
.
If you want it to default to something else you can combine with the above shortcut or to give:
var streetName = (user && user.address && user.address.street) || "Unknown Street";
Solution 3 - Javascript
2020 Update
JavaScript now has equivalents for both the Elvis Operator and the Safe Navigation Operator.
Safe Property Access
The optional chaining operator (?.
) is currently a stage 4 ECMAScript proposal. You can use it today with Babel.
// `undefined` if either `a` or `b` are `null`/`undefined`. `a.b.c` otherwise.
const myVariable = a?.b?.c;
The logical AND operator (&&
) is the "old", more-verbose way to handle this scenario.
const myVariable = a && a.b && a.b.c;
Providing a Default
The nullish coalescing operator (??
) is currently a stage 4 ECMAScript proposal. You can use it today with Babel. It allows you to set a default value if the left-hand side of the operator is a nullary value (null
/undefined
).
const myVariable = a?.b?.c ?? 'Some other value';
// Evaluates to 'Some other value'
const myVariable2 = null ?? 'Some other value';
// Evaluates to ''
const myVariable3 = '' ?? 'Some other value';
The logical OR operator (||
) is an alternative solution with slightly different behavior. It allows you to set a default value if the left-hand side of the operator is falsy. Note that the result of myVariable3
below differs from myVariable3
above.
const myVariable = a?.b?.c || 'Some other value';
// Evaluates to 'Some other value'
const myVariable2 = null || 'Some other value';
// Evaluates to 'Some other value'
const myVariable3 = '' || 'Some other value';
Solution 4 - Javascript
Javascript's logical OR operator is short-circuiting and can replace your "Elvis" operator:
var displayName = user.name || "Anonymous";
However, to my knowledge there's no equivalent to your ?.
operator.
Solution 5 - Javascript
I've occasionally found the following idiom useful:
a?.b?.c
can be rewritten as:
((a||{}).b||{}).c
This takes advantage of the fact that getting unknown attributes on an object returns undefined, rather than throwing an exception as it does on null
or undefined
, so we replace null and undefined with an empty object before navigating.
Solution 6 - Javascript
i think lodash _.get()
can help here, as in _.get(user, 'name')
, and more complex tasks like _.get(o, 'a[0].b.c', 'default-value')
Solution 7 - Javascript
There is currently a draft spec:
https://github.com/tc39/proposal-optional-chaining
https://tc39.github.io/proposal-optional-chaining/
For now, though, I like to use lodash get(object, path [,defaultValue])
or dlv delve(obj, keypath)
Update (as of Dec 23, 2019):
> optional chaining has moved to stage 4
Solution 8 - Javascript
For the former, you can use ||
. The Javascript "logical or" operator, rather than simply returning canned true and false values, follows the rule of returning its left argument if it is true, and otherwise evaluating and returning its right argument. When you're only interested in the truth value it works out the same, but it also means that foo || bar || baz
returns the leftmost one of foo, bar, or baz that contains a true value.
You won't find one that can distinguish false from null, though, and 0 and empty string are false values, so avoid using the value || default
construct where value
can legitimately be 0 or ""
.
Solution 9 - Javascript
Yes, there is!
Optional chaining is in stage 4 and this enables you to use the user?.address?.street
formula.
If you can't wait the release, install @babel/plugin-proposal-optional-chaining
and you can use it.
Here are my settings which works for me, or just read Nimmo's article.
// package.json
{
"name": "optional-chaining-test",
"version": "1.0.0",
"main": "index.js",
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "7.2.0",
"@babel/core": "7.2.0",
"@babel/preset-env": "^7.5.5"
}
...
}
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"debug": true
}
]
],
"plugins": [
"@babel/plugin-proposal-optional-chaining"
]
}
// index.js
console.log(user?.address?.street); // it works
Solution 10 - Javascript
Here's a simple elvis operator equivalent:
function elvis(object, path) {
return path ? path.split('.').reduce(function (nestedObject, key) {
return nestedObject && nestedObject[key];
}, object) : object;
}
> var o = { a: { b: 2 }, c: 3 };
> elvis(o)
{ a: { b: 2 }, c: 3 }
> elvis(o, 'a');
{ b: 2 }
> elvis(o, 'a.b');
2
> elvis(o, 'x');
undefined
Solution 11 - Javascript
You can achieve roughly the same effect by saying:
var displayName = user.name || "Anonymous";
Solution 12 - Javascript
UPDATE SEP 2019
Yes, JS now supports this. Optional chaining is coming soon to v8 read more
Solution 13 - Javascript
This is more commonly known as a null-coalescing operator. Javascript does not have one.
Solution 14 - Javascript
I have a solution for that, tailor it to your own needs, an excerpt from one of my libs:
elvisStructureSeparator: '.',
// An Elvis operator replacement. See:
// http://coffeescript.org/ --> The Existential Operator
// http://fantom.org/doc/docLang/Expressions.html#safeInvoke
//
// The fn parameter has a SPECIAL SYNTAX. E.g.
// some.structure['with a selector like this'].value transforms to
// 'some.structure.with a selector like this.value' as an fn parameter.
//
// Configurable with tulebox.elvisStructureSeparator.
//
// Usage examples:
// tulebox.elvis(scope, 'arbitrary.path.to.a.function', fnParamA, fnParamB, fnParamC);
// tulebox.elvis(this, 'currentNode.favicon.filename');
elvis: function (scope, fn) {
tulebox.dbg('tulebox.elvis(' + scope + ', ' + fn + ', args...)');
var implicitMsg = '....implicit value: undefined ';
if (arguments.length < 2) {
tulebox.dbg(implicitMsg + '(1)');
return undefined;
}
// prepare args
var args = [].slice.call(arguments, 2);
if (scope === null || fn === null || scope === undefined || fn === undefined
|| typeof fn !== 'string') {
tulebox.dbg(implicitMsg + '(2)');
return undefined;
}
// check levels
var levels = fn.split(tulebox.elvisStructureSeparator);
if (levels.length < 1) {
tulebox.dbg(implicitMsg + '(3)');
return undefined;
}
var lastLevel = scope;
for (var i = 0; i < levels.length; i++) {
if (lastLevel[levels[i]] === undefined) {
tulebox.dbg(implicitMsg + '(4)');
return undefined;
}
lastLevel = lastLevel[levels[i]];
}
// real return value
if (typeof lastLevel === 'function') {
var ret = lastLevel.apply(scope, args);
tulebox.dbg('....function value: ' + ret);
return ret;
} else {
tulebox.dbg('....direct value: ' + lastLevel);
return lastLevel;
}
},
works like a charm. Enjoy the less pain!
Solution 15 - Javascript
You could roll your own:
function resolve(objectToGetValueFrom, stringOfDotSeparatedParameters) {
var returnObject = objectToGetValueFrom,
parameters = stringOfDotSeparatedParameters.split('.'),
i,
parameter;
for (i = 0; i < parameters.length; i++) {
parameter = parameters[i];
returnObject = returnObject[parameter];
if (returnObject === undefined) {
break;
}
}
return returnObject;
};
And use it like this:
var result = resolve(obj, 'a.b.c.d');
* result is undefined if any one of a, b, c or d is undefined.
Solution 16 - Javascript
I read this article (https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript) and modified the solution using Proxies.
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.nullObj;
},
});
}
safe.nullObj = safe({});
safe.safeGet= function(obj, expression) {
let safeObj = safe(obj);
let safeResult = expression(safeObj);
if (safeResult === safe.nullObj) {
return undefined;
}
return safeResult;
}
You call it like this:
safe.safeGet(example, (x) => x.foo.woo)
The result will be undefined for an expression that encounters null or undefined along its path. You could go wild and modify the Object prototype!
Object.prototype.getSafe = function (expression) {
return safe.safeGet(this, expression);
};
example.getSafe((x) => x.foo.woo);
Solution 17 - Javascript
Jumping in very late, there's a proposal[1] for optional chaining currently at stage 2, with a babel plugin[2] available. It's not currently in any browser I am aware of.
Solution 18 - Javascript
This was a problem for me for a long time. I had to come up with a solution that can be easily migrated once we get Elvis operator or something.
This is what I use; works for both arrays and objects
put this in tools.js file or something
// this will create the object/array if null
Object.prototype.__ = function (prop) {
if (this[prop] === undefined)
this[prop] = typeof prop == 'number' ? [] : {}
return this[prop]
};
// this will just check if object/array is null
Object.prototype._ = function (prop) {
return this[prop] === undefined ? {} : this[prop]
};
usage example:
let student = {
classes: [
'math',
'whatev'
],
scores: {
math: 9,
whatev: 20
},
loans: [
200,
{ 'hey': 'sup' },
500,
300,
8000,
3000000
]
}
// use one underscore to test
console.log(student._('classes')._(0)) // math
console.log(student._('classes')._(3)) // {}
console.log(student._('sports')._(3)._('injuries')) // {}
console.log(student._('scores')._('whatev')) // 20
console.log(student._('blabla')._('whatev')) // {}
console.log(student._('loans')._(2)) // 500
console.log(student._('loans')._(1)._('hey')) // sup
console.log(student._('loans')._(6)._('hey')) // {}
// use two underscores to create if null
student.__('loans').__(6)['test'] = 'whatev'
console.log(student.__('loans').__(6).__('test')) // whatev
well I know it makes the code a bit unreadable but it's a simple one liner solution and works great. I hope it helps someone :)
Solution 19 - Javascript
This was an interesting solution for the safe navigation operator using some mixin..
http://jsfiddle.net/avernet/npcmv/
// Assume you have the following data structure
var companies = {
orbeon: {
cfo: "Erik",
cto: "Alex"
}
};
// Extend Underscore.js
_.mixin({
// Safe navigation
attr: function(obj, name) { return obj == null ? obj : obj[name]; },
// So we can chain console.log
log: function(obj) { console.log(obj); }
});
// Shortcut, 'cause I'm lazy
var C = _(companies).chain();
// Simple case: returns Erik
C.attr("orbeon").attr("cfo").log();
// Simple case too, no CEO in Orbeon, returns undefined
C.attr("orbeon").attr("ceo").log();
// IBM unknown, but doesn't lead to an error, returns undefined
C.attr("ibm").attr("ceo").log();
Solution 20 - Javascript
I created a package that makes this a lot easier to use.
You can handle simple things like and object:
const world = {
locations: {
europe: 'Munich',
usa: 'Indianapolis'
}
};
world.dig('locations', 'usa');
// => 'Indianapolis'
world.dig('locations', 'asia', 'japan');
// => 'null'
or a little more complicated:
const germany = () => 'germany';
const world = [0, 1, { location: { europe: germany } }, 3];
world.dig(2, 'location', 'europe') === germany;
world.dig(2, 'location', 'europe')() === 'germany';
Solution 21 - Javascript
??
would work in js which is equivalent to ?:
in kotlin
Solution 22 - Javascript
Personally i use
function e(e,expr){try{return eval(expr);}catch(e){return null;}};
and for example safe get:
var a = e(obj,'e.x.y.z.searchedField');