Click here to Skip to main content
15,890,882 members
Articles / Programming Languages / Javascript

Safely Navigating Object Hierarchies in JavaScript using Prototype Methods

Rate me:
Please Sign up or sign in to vote.
4.00/5 (3 votes)
14 Aug 2017CPOL1 min read 5.8K   3
How to safely navigate object hierarchies in JavaScript using Prototype methods

Anyone who’s dealt with a deeply nested set of properties in JavaScript, whether through use of an extensive third-party JavaScript API or a custom library, has likely run into the problem of safely accessing such structures. One can of course hand-roll many ugly, hard-to-read statements like the following, adding on to the maintenance burden of the code (splitting lines as necessary for length, of course):

JavaScript
if (a && a.b && a.b.c && a.b.c.d && a.b.c.d.e) { doSomethingWith(a.b.c.d.e); }

The main alternative to this approach is to make such accesses within a try-catch block, but this is generally slower by at least a couple of orders of magnitude when exceptions are thrown, so not always useful in tight loops and other performance-sensitive situations. It’s also arguably an abuse of the try/catch mechanism.

Luckily, a less unsavory solution with fairly good performance can be adopted using JavaScript prototype methods. Here’s a reference implementation, and you can also try it for yourself (with timings):

JavaScript
// Indicates whether an object has the indicated nested subproperty, 
// which may be specified with chained dot notation 
// or as separate string arguments.
Object.prototype.hasSubproperty = function() {
	if (arguments.length == 0 || typeof(arguments[0]) != 'string') return false;  
  var properties = arguments[0].indexOf('.') > -1 ? arguments[0].split('.') : arguments;    
  var current = this;
  for(var x = 0; x  -1 ? arguments[0].split('.') : arguments;    
  var current = this;
  for(var x = 0; x < properties.length; x++) {
  	current = current[properties[x]];
    if ((typeof current) == 'undefined') return undefined;
  }  
  return current;
};

// Gets the indicated nested subproperty, which may be specified with chained dot notation 
// or as separate arguments.
// If the specified subproperty (or any intervening object in the hierarchy) is not found, 
// returns undefined.
Object.prototype.getSubproperty = function() {
	if (arguments.length == 0 || typeof(arguments[0]) != 'string') return false;  
  var properties = arguments[0].indexOf('.') > -1 ? arguments[0].split('.') : arguments;    
  var current = this;
  for(var x = 0; x < properties.length; x++) {
  	current = current[properties[x]];
    if ((typeof current) == 'undefined') return undefined;
  }  
  return current;
};

// Sets the indicated nested subproperty, which may be specified with chained dot notation 
// or as separate arguments.
// If any intervening object in the hierarchy is not found, returns false, 
// otherwise sets the value and returns true.
Object.prototype.setSubproperty = function() {
	if (arguments.length  -1 ? arguments[0].split('.') : 
          Array.prototype.slice.call(arguments, 0, arguments.length - 1);    
  var parent, current = this;
  for(var x = 0; x < properties.length - 1; x++) {
  	current = current[properties[x]];
    if ((typeof current) == 'undefined') return false;
  }  
  current[properties[properties.length - 1]] = arguments[arguments.length - 1];
  return true;
};

Some observations: If you run the timings, you’ll note that the try-catch method is still quite fast when exceptions are not thrown, indicating that try-catch might be workable when exceptions are expected to be truly… exceptional. Still, in any but extraordinary conditions, the performance of the prototype-method approach should be quite fast enough, avoids worst-case performance, and is cleanest overall.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralFew observations Pin
Booster2ooo19-Aug-17 22:38
Booster2ooo19-Aug-17 22:38 
Jeff Varszegi,


First of all, thanks for taking the time to write this article.

However, there are a few things that don't seem correct to me.

Here are my remarks:

- The code sample doesn't run
for(var x = 0; x -1 ? arguments[0].split('.') : arguments;

(Edit: bad CodeProject formatting, check JSFiddle sample)

- Extending native objects prototypes is considered a bad practice

- When traveling accros object properties, you'll rather use foreach loops and make sure the object "hasOwnProperty" instead of for(var loops
(Edit: the point here is about check for object own property, not the way you can write loops)

- Using ES6, you can directly get 'own' properties keys of an object using Object.keys(obj)


I think you should rather end up with a function that may look like:
JavaScript
var getSubproperty = function getSubproperty(target, property) {
    var propertyPath = property.split('.')
      , lastFound = target
      ;
    propertyPath.forEach(function(prop) {
        if(!lastFound.hasOwnProperty(prop) || typeof(lastFound[prop]) === typeof(undefined)) {
            lastFound = null;
            return false;
        }
        lastFound = lastFound[prop];
    });
    return lastFound;
};


And exemple using ES6 (from Using ES6's Proxy for safe Object property access[^] ):

JavaScript
const DarthVader = {
  name : 'Anakin',
  mother : {
    name : 'Shmi'
  }
};

function deepAccessUsingString(obj, key){
  return key.split('.').reduce((nestedObject, key) => {
    if(nestedObject && key in nestedObject) {
      return nestedObject[key];
    }
    return undefined;
  }, obj);
}

let mothersName = deepAccessUsingString(DarthVader, 'mother.name');
let fathersName = deepAccessUsingString(DarthVader, 'father.name');
  
console.log(mothersName); // prints "Shmi"
console.log(fathersName); // prints undefined



Hope it helps Smile | :)

modified 26-Aug-17 6:07am.

GeneralRe: Few observations Pin
Jeff Varszegi22-Aug-17 1:33
professionalJeff Varszegi22-Aug-17 1:33 
GeneralRe: Few observations Pin
Booster2ooo25-Aug-17 23:57
Booster2ooo25-Aug-17 23:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.