DEV Community

loading...

Optional (null-safe) in javascript

J. Pichardo
Just another hacker doing hacky stuff.
・Updated on ・3 min read

Yesterday I ran into this StackOverflow question and it got me thinking about null/undefined handling in javascript.

A short background

What is undefined? undefined is a primitive value that is given to variables that have only been declared, non-existent properties or function arguments

What is null? null is another primitive value that represents the absence of value.

So what happens when we do the following


let obj;

console.log(obj.someProp);

Enter fullscreen mode Exit fullscreen mode

We get the following error

TypeError: Cannot read property 'someProp' of undefined

And the same happens with null

null-checking

So, how can we avoid that? Well, lucky for us in javascript we have short-circuit evaluation, meaning that in order to avoid our previous TypeError we could write the following.


let obj;

console.log(obj && obj.someProp); // Prints undefined

Enter fullscreen mode Exit fullscreen mode

But what if we want to go deeper, something like obj.prop1.prop2.prop3? We would end doing lots of checks, like:


console.log( obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.prop3 );

Enter fullscreen mode Exit fullscreen mode

Seems obnoxious, doesn't it?

And what if we wanted to print a default value if there was an undefined or null in that chain? Then it would be an even larger expression:


const evaluation = obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.prop3;

console.log( evaluation != null ? evaluation : "SomeDefaultValue" );

Enter fullscreen mode Exit fullscreen mode

How do other languages do it?

This issue is not exclusive of javascript, it is present in most programming languages, so let's see how to do null-checking in some of them.

Java

In java we have the Optional API:


SomeClass object;

Optional.ofNullable(object)
    .map(obj -> obj.prop1)
    .map(obj -> obj.prop2)
    .map(obj -> obj.prop3)
    .orElse("SomeDefaultValue");

Enter fullscreen mode Exit fullscreen mode

Kotlin

In kotlin (another JVM language) there are the elvis (?:) and safe-call (?.) operators.


val object: SomeClass?

object?.prop1?.prop2?.prop3 ?: "SomeDefaultValue";

Enter fullscreen mode Exit fullscreen mode

C#

Finally, in c# we also have the null-condition (?.) and null-coalescing (??) operators.


SomeClass object;

object?.prop1?.prop2?.prop3 ?? "SomeDefaultValue";

Enter fullscreen mode Exit fullscreen mode

JS Options

So after seeing all this, I was wondering, isn't there a way to avoid writing that much in javascript?, so I started experimenting with regex to write a function that'd allow me to access an object property safely.


function optionalAccess(obj, path, def) {
  const propNames = path.replace(/\]|\)/, "").split(/\.|\[|\(/);

  return propNames.reduce((acc, prop) => acc[prop] || def, obj);
}

const obj = {
  items: [{ hello: "Hello" }]
};

console.log(optionalAccess(obj, "items[0].hello", "def")); // Prints Hello
console.log(optionalAccess(obj, "items[0].he", "def")); // Prints def

Enter fullscreen mode Exit fullscreen mode

And after that, I found about lodash._get, which has the same signature:

_.get(object, path, [defaultValue])

But to be honest I'm not that much a fan of string paths so I started searching a way to avoid them, then I came with a solution using proxies:


// Here is where the magic happens
function optional(obj, evalFunc, def) {

  // Our proxy handler
  const handler = {
    // Intercept all property access
    get: function(target, prop, receiver) {
      const res = Reflect.get(...arguments);

      // If our response is an object then wrap it in a proxy else just return
      return typeof res === "object" ? proxify(res) : res != null ? res : def;
    }
  };

  const proxify = target => {
    return new Proxy(target, handler);
  };

  // Call function with our proxified object
  return evalFunc(proxify(obj, handler));
}

const obj = {
  items: [{ hello: "Hello" }]
};

console.log(optional(obj, target => target.items[0].hello, "def")); // => Hello
console.log(optional(obj, target => target.items[0].hell, { a: 1 })); // => { a: 1 }

Enter fullscreen mode Exit fullscreen mode

The future

Currently, there is a TC39 proposal that will allow us to do the following:


obj?.arrayProp?[0]?.someProp?.someFunc?.();

Enter fullscreen mode Exit fullscreen mode

Looks pretty neat right? However, this proposal is still in stage 1, which means it will probably take a while before we can see this is js. Nonetheless, there is a babel plugin that allows us to use that syntax.

Finale

null has been and will be around for a while and I bet is one of the most hated concepts in programming, however, there are ways to procure null-safety. Here I've posted my two cents, let me know what you think or if you have any other alternatives.

p.s.: Here is a little pretty gist

Discussion (5)

Collapse
ehsansarshar profile image
Ehsan sarshar

there is a babel plugin that can solve your problem
I think it is
@babel/plugin-proposal-optional-chaining

Collapse
pichardoj profile image
J. Pichardo Author

Yeah, I know about it, but the idea is to suggest a solution that doesn't require external plugins.

Collapse
alexkubica profile image
Alex Kubica

Thanks or the post!
As of December 2019 the proposal has reached the final stage:
github.com/tc39/proposals/blob/mas...
I'm really excited 😁

Collapse
mohkamfer profile image
Muhammad Kamal Fergany

According to that link, it's in Stage 3 as of the writing of this comment.

Collapse
nmyers322 profile image
Nathanial Myers

Finally on Stage 4...