A nullsafe traversal
npm install nullsafeA nice way to do a nullsafe traversal of nested objects.
``javascript`
const pet = {
"biggest": {
"kitty": {
"name": "Missy"
}
}
};
You would normally have to use a few null checks in order to pull the name (Missy) out of there.
`javascript`
let name;
if (pet && pet.biggest && pet.biggest.kitty) {
name = pet.biggest.kitty.name;
}
Alternatively, you could also use lodash
`javascript`
const _ = require('lodash');
const name = _.get(pet, 'biggest.kitty.name');
But I wanted to be able to pass around optional objects, objects that could either contain a value or not. And I wanted these special optional objects to be safe against anything I invoked against it.
Lastly, I didn't want to care about the differences between null and undefined. This library will treat the two as the same and always return null when the target (thing being wrapped) is either a null or undefined.
`javascript
const nullsafe = require('nullsafe');
const person = {
name: 'Jon'
};
const zip = nullsafe(person)
.get('address') // does not exist
.get('zip'); // does not exist
const isNull = zip.isNull(); // true
const value = zip.value; // null
`
`javascript
const nullsafe = require('nullsafe');
const nullableName = nullsafe(pet, ['biggest', 'kitty', 'name']);
const name = nullableName.value;
// or just this
// const name = nullsafe(pet, ['biggest', 'kitty', 'name']).value;
`
The advantage of immediately resolving the path is that no intermediate objects are created. In this example above, no proxy objects are created for pet, biggest, or kitty; only the final attribute name is wrapped.
`javascript`
const nullsafe = require('nullsafe');
const name = nullsafe(pet)
.get('biggest')
.get('kitty')
.get('name').value;
The way it works is simple. The nullsafe method wraps your object in a proxy. You then call the get method to get the attribute you want out. You keep doing this all the way down the chain until you are done. Then you call value in order to unwrap the object. If anywhere in the chain failed, you will get a null back when you unwrap it at the end. Example:
`javascript`
const let = nullsafe(pet)
.get('biggest')
.get('doggy')
.get('name').value;
We don't have a doggy yet, but the traversal will not fail and will return a null at the end. This works no matter how long the chain:
`javascript`
const nameOfMainAttraction = nullsafe(pet)
.get('biggest')
.get('parrot')
.get('name')
.get('otherStuffThatDoesntExist').value;
also supports function calls in the chain.
`javascript
const kennel = {
"getSupervisorInfo": () => {
return {
"name": "Billy",
"shift": "Daytime"
};
}
}; const supervisorName = nullsafe(kennel).call('getSupervisorInfo').get('name').value;
// Billy
`Notice the
call method that will invoke the getSupervisorInfo method. Call then wraps the results in a proxy object that we can then chain against. You could of course chain get or call for as long as you want.We could also pass in parameters:
`javascript
const mathFunctions = {
"addTwo": (num) => {
return num + 2;
},
"multiplyTogether": (first, second) => {
return first * second;
}
}; const math = nullsafe(mathFunctions);
const sum = math.call('addTwo', 5).value; // 7
const product = math.call('multiplyTogether', 10, 4).value; // 40
`Or if you prefer apply over call, which is basically the same thing except that the arguments are all passed in a single array:
`javascript
const mathFunctions = {
"addTwo": (num) => {
return num + 2;
},
"multiplyTogether": (first, second) => {
return first * second;
}
}; const math = nullsafe(mathFunctions);
const sum = math.apply('addTwo', [5]).value; // 7
const product = math.apply('multiplyTogether', [10, 4]).value; // 40
`
Arrays
Array dereferencing is also supported.
`javascript
const school = {
"students": [
{
"name": "Jane"
},
{
"name": "Robin"
}
]
}; const firstStudentName = nullsafe(school).get('students', 0).get('name').value; // Jane
const bogusStudentName = nullsafe(school).get('students', 100).get('name').value; // null
`To access elements in an array, you just pass as the second argument to
get the array index that you want to access.
Unnested Objects
You could also use it to null check non-nested objects as well.
`javascript
const nullThing = null;
const someFunction = () => "hi";
const someArray = [100, 200]; const greeting = nullsafe(someFunction).call().value; // hi
const num = nullsafe(someArray).get(1).value; // 200
const nullFunction = nullsafe(nullThing).call().value; // null
const nullArrayAccess = nullsafe(nullThing).get(2).value; // null
const nullObjectAccess = nullsafe(nullThing).get('id').value; // null
`Raw Value
In the rare cases you care if the value that is wrapped is an actual undefined vs null value, you can get that by calling rawValue.
`javascript
// rawValue
const undefinedThing = nullsafe(undefined).rawValue; // undefined
const nullThing = nullsafe(null).rawValue; // null
const name = nullsafe('jon').rawValue; // jon // value
const undefinedThing2 = nullsafe(undefined).value; // null
const nullThing2 = nullsafe(null).value; // null
const name2 = nullsafe('jon').value; // jon
`Performance
Benchmarks performed using Benchmark.js. Benchmark code can be found here.Run on:
* MacBook Pro (15-inch, 2016)
* macOS High Sierra 10.13.4 (17E202)
* Processor 2.6 GHz Intel Core i7
* Memory 16 GB 2133 MHz LPDDR3
Comparing:
1. Lodash v4.17.11
2. Ramda v0.26.1
3. Nullsafe v2.0.2
Run 1
`
Lodash#get x 3,352,635 ops/sec ±3.80% (79 runs sampled)
Ramda#path x 16,985,122 ops/sec ±2.32% (84 runs sampled)
Nullsafe#value x 17,435,296 ops/sec ±1.96% (76 runs sampled)
Nullsafe#rawValue x 17,489,960 ops/sec ±1.37% (88 runs sampled)
Fastest is Nullsafe#rawValue,Nullsafe#value,Ramda#path
`Run 2
`
Lodash#get x 3,807,708 ops/sec ±2.22% (84 runs sampled)
Ramda#path x 18,124,155 ops/sec ±1.67% (89 runs sampled)
Nullsafe#value x 17,382,109 ops/sec ±1.51% (83 runs sampled)
Nullsafe#rawValue x 17,557,116 ops/sec ±1.26% (86 runs sampled)
Fastest is Ramda#path
`Run 3
`
Lodash#get x 3,856,657 ops/sec ±1.90% (86 runs sampled)
Ramda#path x 17,466,883 ops/sec ±2.48% (86 runs sampled)
Nullsafe#value x 17,630,377 ops/sec ±1.03% (89 runs sampled)
Nullsafe#rawValue x 17,630,998 ops/sec ±1.07% (89 runs sampled)
Fastest is Nullsafe#value,Nullsafe#rawValue,Ramda#path
`Run 4
`
Lodash#get x 3,864,329 ops/sec ±2.37% (77 runs sampled)
Ramda#path x 18,743,263 ops/sec ±1.83% (80 runs sampled)
Nullsafe#value x 17,284,128 ops/sec ±1.59% (86 runs sampled)
Nullsafe#rawValue x 17,538,949 ops/sec ±1.36% (89 runs sampled)
Fastest is Ramda#path
`Run 5
`
Lodash#get x 4,136,289 ops/sec ±1.94% (81 runs sampled)
Ramda#path x 18,449,161 ops/sec ±1.07% (86 runs sampled)
Nullsafe#value x 17,517,517 ops/sec ±1.33% (84 runs sampled)
Nullsafe#rawValue x 17,648,284 ops/sec ±1.35% (87 runs sampled)
Fastest is Ramda#path
``