nullsafe
Version:
A nullsafe traversal
282 lines (220 loc) • 8.68 kB
Markdown
# Nullsafe
[](https://badge.fury.io/js/nullsafe)
[](https://travis-ci.org/jkeam/nullsafe)
[](https://coveralls.io/github/jkeam/nullsafe?branch=master)
A nice way to do a nullsafe traversal of nested objects.
## Supported Node Versions
* 11
* 10
* 9
* 8
* 7
* 6
## Motivation
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:
```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`.
# Usage
Include the library and wrap your object.
```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
```
## Api
### Immediately Resolve
If you have an object, and want to immediately evaluate some path, you can. For example:
```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.
### Objects
The most common use case is for passing around objects that could be null.
```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;
```
### Functions
`nullsafe` 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](https://github.com/bestiejs/benchmark.js/). Benchmark code can be found [here](https://github.com/jkeam/nullsafe_benchmark).
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
```