A nice way to do a nullsafe traversal of nested objects.
- 11
- 10
- 9
- 8
- 7
- 6
If you have nested objects and want a way to safely traverse it, nullsafe will help you. Let's take an example. Given this object:
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.
let name;
if (pet && pet.biggest && pet.biggest.kitty) {
name = pet.biggest.kitty.name;
}
Alternatively, you could also use lodash
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
.
Include the library and wrap your object.
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
If you have an object, and want to immediately evaluate some path, you can. For example:
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.
The most common use case is for passing around objects that could be null.
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:
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:
const nameOfMainAttraction = nullsafe(pet)
.get('biggest')
.get('parrot')
.get('name')
.get('otherStuffThatDoesntExist').value;
nullsafe
also supports function calls in the chain.
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:
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:
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
Array dereferencing is also supported.
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.
You could also use it to null check non-nested objects as well.
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
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
.
// 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
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:
- Lodash v4.17.11
- Ramda v0.26.1
- 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