🔗 Nest Safely
TL;DR:
Turn this:
const data = { deeply : { nested : { source : { why : 42 } } } }; if (data.deeply && data.deeply.nested && data.deeply.nested.source) { console.log(data.deeply.nested.source.why);}
Into this:
const data = { deeply : { nested : { source : { why : 42 } } } }; console.log( Safe(data).deeply.nested.source.why [Safe.or]("Nothing found.") [Safe.value]);
The Details
Despite allowing remarkable flexibility and ease of use, when accessing objects in JavaScript it doesn't take long before you run into this sort of error:
const data = { deeply : { nested : { source : 42 } } };const val = data.deeply.not.here; // Uncaught TypeError: Cannot read property 'here' of undefined// at <anonymous>:2:29
Many libraries exist to solve the problem of accessing deeply nested values in an object, but they all seem to use evaluated strings of access keys like:
const data = { deeply : { nested : { source : 42 } } };const val = access(data, "deeply.not.here", "nothing");console.log(val); // nothing
I found this too awkward to use in my code, so I decided to make an improvement using functional programming! I implemented a basic Maybe
monad, then used the shiny new ES6 Proxy
object to allow for deep object access (and more!) using minimally added syntax. Here's an example:
const Safe = require("nest-safely"); const data = Safe({ deeply : { nested : { source : 42 } } }); const val = data.deeply.no.way.anything.is.here[Safe.or]("nothing was found")[Safe.value]; console.log(val); // "nothing was found"
Notice the use of an additional term [Safe.or]
in the chain. You can think of each .
in the sequence as a .then
in a Promise chain inasmuch as any undefined access will fall through to the end. In this case, a default value is provided to the [Safe.or]
method which will not have its functionality invoked if the value has not dropped out in the sequence:
const Safe = require("nest-safely"); const data = Safe({ deeply : { nested : { source : 42 } } }); const val = data.deeply.nested.source.[Safe.or]("nothing was found")[Safe.value]; console.log(val); // 42
The only other piece of machinery added is a [Safe.value]
getter keyword that will unwrap the value out of the chain. Optionally, a [Safe.handle]
method may also be used where you can supply a function that will be given the last value seen in the chain before null
or undefined
was encountered.
const Safe = require("nest-safely"); const data = Safe({ deeply : { nested : { source : 42 } } }); const val = data.deeply.nested.does.not.have [Safe.handle](last => `Last seen: ${JSON.stringify(last)}`) [Safe.value]; console.log(val); // Last seen: { "source" : 42 }
And, just like a Promise
chain, [Safe.or]
or [Safe.handle]
can be located anywhere in the chain:
const Safe = require("nest-safely"); const data = Safe({ deeply : { nested : { source : 42 } } }); const val = data.deeply.nested.does.not.have [Safe.or]({ x : { y : 34 } }) .x .nope [Safe.handle](last => `Last seen: ${JSON.stringify(last)}`) [Safe.value]; console.log(val); // Last seen: { "y" : 42 }
I hope this small 40-line library is useful for you! If you have suggestions, make an issue and I'll be happy to look over it.