npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

nullsafe

v2.0.2

Published

A nullsafe traversal

Downloads

41

Readme

Nullsafe

npm version Build Status Coverage Status

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:

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.

Usage

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

Api

Immediately Resolve

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.

Objects

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;

Functions

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

Arrays

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.

Unnested Objects

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

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.

// 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