undefsafe.js 2.48 KB
'use strict';

function undefsafe(obj, path, value, __res) {

  // I'm not super keen on this private function, but it's because
  // it'll also be use in the browser and I wont *one* function exposed
  function split(path) {
    var res = [];
    var level = 0;
    var key = '';

    for (var i = 0; i < path.length; i++) {
      var c = path.substr(i, 1);

      if (level === 0 && (c === '.' || c === '[')) {
        if (c === '[') {
          level++;
          i++;
          c = path.substr(i, 1);
        }

        if (key) { // the first value could be a string
          res.push(key);
        }
        key = '';
        continue;
      }

      if (c === ']') {
        level--;
        key = key.slice(0, -1);
        continue;
      }

      key += c;
    }

    res.push(key);

    return res;
  }

  // bail if there's nothing
  if (obj === undefined || obj === null) {
    return undefined;
  }

  var parts = split(path);
  var key = null;
  var type = typeof obj;
  var root = obj;
  var parent = obj;

  var star = parts.filter(function (_) { return _ === '*' }).length > 0;

  // we're dealing with a primative
  if (type !== 'object' && type !== 'function') {
    return obj;
  } else if (path.trim() === '') {
    return obj;
  }

  key = parts[0];
  var i = 0;
  for (; i < parts.length; i++) {
    key = parts[i];
    parent = obj;

    if (key === '*') {
      // loop through each property
      var prop = '';
      var res = __res || [];

      for (prop in parent) {
        var shallowObj = undefsafe(obj[prop], parts.slice(i + 1).join('.'), value, res);
        if (shallowObj && shallowObj !== res) {
          if ((value && shallowObj === value) || (value === undefined)) {
            if (value !== undefined) {
              return shallowObj;
            }

            res.push(shallowObj);
          }
        }
      }

      if (res.length === 0) {
        return undefined;
      }

      return res;
    }

    obj = obj[key];
    if (obj === undefined || obj === null) {
      break;
    }
  }

  // if we have a null object, make sure it's the one the user was after,
  // if it's not (i.e. parts has a length) then give undefined back.
  if (obj === null && i !== parts.length - 1) {
    obj = undefined;
  } else if (!star && value) {
    key = path.split('.').pop();
    parent[key] = value;
  }
  return obj;
}

if (typeof module !== 'undefined') {
  module.exports = undefsafe;
}