4
\$\begingroup\$

I'm refactoring some code, essentially a dot-notation walker for objects. I've abstracted things quite a bit, I'm to the point any more and I begin to loose readability.

It all hinges on this one function objectWalker, which does the actual object traversals. I can control what happens by passing in different callbacks to handle key points in the processes.

function objectWalker (paths, obj, onEnd, enRoute, onEach) {
      idx = -1,
      nextContext = prevContext = obj;
      const len = paths ? paths.length : 0;
      const lastStep = len ? len - 1 : 0;
      while (nextContext != null && ++idx < len) {
        if (onEach) nextContext = onEach(nextContext, idx, paths, prevContext);
        if (idx !== lastStep) {
          prevContext = nextContext;
          nextContext = (!dotWalker.objectHas(nextContext, paths[idx])) ? enRoute(nextContext, idx, paths, prevContext) : nextContext[paths[idx]];
        } else {
          prevContext = paths.length === 1 ? nextContext : prevContext[paths[idx - 1]];
          return onEnd(nextContext, idx, paths, prevContext );
        }
        if(!nextContext) return nextContext;
      }
      return nextContext;
    }

Pretty simple, and here are some of the callbacks i can send in.

dotWalker.onEnd = function(context, idx, dotPathss, prevContext) {
  if (idx === 0) {
    return dotWalker.objectHas(context, dotPathss[idx]) ? context[dotPathss[idx]] : undefined;
  } else {
    return prevContext[dotPathss[idx]];
  }
}

dotWalker.onEndAssigner = function(value){
  return function(context, idx, dotPathss, prevContext) {
    return prevContext[dotPathss[idx]] = value, prevContext[dotPathss[idx]];
  }
}


dotWalker.enRouteMapper = function(context, idx, dotPathss, prevContext) {
    return context[dotPathss[idx]] = {}, context[dotPathss[idx]];
}

dotWalker.enRoute = function(context, idx, dotPathss, prevContext) {
  return dotWalker.objectHas(context, dotPathss[idx]) ? context[dotPathss[idx]] : undefined ;
}

dotWalker.onEach = function(context, idx, dotPaths, prevContext) {
  return context;
}

I'm pretty pleased with the above, the issue i have is with the following "pathFinder" function. It seems to me to be doing too much, but I can't decide where to break it down and refactor further.

    dotWalker.pathFinder = function(paths, c, namespace, pd, nd) {
  if (!c && !paths) return [];
  pd = pd || '.';
  nd = nd || ':';
  const lastChar = paths[paths.length - 1];
  const isNamespacedDotPath = paths.indexOf(nd) > -1;
  if ( !!dotWalker.objectHas(c, 'ns') ) {
    if ( lastChar === nd ) { // "label:" -> namespaced but no dotPaths
      namespace = c[paths.slice(0, -1)], paths = undefined;
    } 
    if ( isNamespacedDotPath ) { // "label:some.sub.path" -> namespaced but with dotPaths
      paths = paths.split(nd), namespace = c.ns[paths[0]], paths = paths.slice(1)[0].split(pd);
    }
    if(!namespace){
      paths = paths.split(pd), namespace = c.selected || c.ns.root;
    }
  } else { /* Attempting to use namespaceses on simple object */
    if ( isNamespacedDotPath ) {
      paths = paths.split(nd);
      if( !c || !dotWalker.objectHas(c, paths[0])){
         namespace = undefined, paths = [];
      } else {
         namespace = c[paths[0]], paths = paths.slice(1).join('.').split(pd);
      }
    }
    if (typeof paths === 'string' && paths.indexOf(pd) > -1) {
      paths = paths.split(pd);
    }
    if ( lastChar === nd){
      paths = '';
    }
    namespace = namespace || c;
  }
  paths = paths[0] === '' || !paths ? undefined : [].concat(paths);
  return !c ? paths : { dotPaths: paths, context: namespace };
}

Any feedback is welcome, thanks.

I've included the full code here in a little snippet, you can run it to see how it works. I've also included a crude suite of tests, to highlight intended behavior.

I've added two more functions to demonstrate how one might leverage the objectWalker.

dotWalker.assign = function (dotPath, value, context) {
  return dotWalker(dotPath, context, dotWalker.onEndAssigner(value), dotWalker.enRouteMapper);
}

dotWalker.get = function (dotPath, context) {
  return dotWalker(dotPath, context, dotWalker.onEnd, dotWalker.enRoute);
}

And I added two more tests demonstrating their use.

  /**
        DotWalker accepts a path a context/object and two callbacks. 
        The path, can be a dotPaths, as the context, is being walked
  */
  const dotWalker = (function() {

    const dotWalker = function (dotPaths, context, end, enRoute, onEach){
      end = end || dotWalker.onEnd; 
      enRoute = enRoute || dotWalker.enRoute;
      var { context, dotPaths } = dotWalker.pathFinder( dotPaths || '', context);
      return objectWalker(dotPaths, context, end, enRoute, onEach);
    }

    dotWalker.assign = function (dotPath, value, context) {
      return dotWalker(dotPath, context, dotWalker.onEndAssigner(value), dotWalker.enRouteMapper);
    }

    dotWalker.pathFinder = function(paths, c, namespace, pd, nd) {
      if (!c && !paths) return [];
      pd = pd || '.';
      nd = nd || ':';
      const lastChar = paths[paths.length - 1];
      const isNamespacedDotPath = paths.indexOf(nd) > -1;
      if ( !!dotWalker.objectHas(c, 'ns') ) {
        if ( lastChar === nd ) { // "label:" -> namespaced but no dotPaths
          namespace = c[paths.slice(0, -1)], paths = undefined;
        } 
        if ( isNamespacedDotPath ) { // "label:some.sub.path" -> namespaced but with dotPaths
          paths = paths.split(nd), namespace = c.ns[paths[0]], paths = paths.slice(1)[0].split(pd);
        }
        if(!namespace){
          paths = paths.split(pd), namespace = c.selected || c.ns.root;
        }
      } else { /* Attempting to use namespaceses on simple object */
        if ( isNamespacedDotPath ) {
          paths = paths.split(nd);
          if( !c || !dotWalker.objectHas(c, paths[0])){
             namespace = undefined, paths = [];
          } else {
             namespace = c[paths[0]], paths = paths.slice(1).join('.').split(pd);
          }
        }
        if (typeof paths === 'string' && paths.indexOf(pd) > -1) {
          paths = paths.split(pd);
        }
        if ( lastChar === nd){
          paths = '';
        }
        namespace = namespace || c;
      }
      paths = paths[0] === '' || !paths ? undefined : [].concat(paths);
      return !c ? paths : { dotPaths: paths, context: namespace };
    }

    function objectWalker (paths, obj, onEnd, enRoute, onEach) {
      idx = -1,
      nextContext = prevContext = obj;
      const len = paths ? paths.length : 0;
      const lastStep = len ? len - 1 : 0;
      while (nextContext != null && ++idx < len) {
        if (onEach) nextContext = onEach(nextContext, idx, paths, prevContext);
        if (idx !== lastStep) {
          prevContext = nextContext;
          nextContext = (!dotWalker.objectHas(nextContext, paths[idx])) ? enRoute(nextContext, idx, paths, prevContext) : nextContext[paths[idx]];
        } else {
          prevContext = paths.length === 1 ? nextContext : prevContext[paths[idx - 1]];
          return onEnd(nextContext, idx, paths, prevContext );
        }
        if(!nextContext) return nextContext;
      }
      return nextContext;
    }

    dotWalker.onEnd = function(context, idx, dotPathss, prevContext) {
      if (idx === 0) {
        return dotWalker.objectHas(context, dotPathss[idx]) ? context[dotPathss[idx]] : undefined;
      } else {
        return prevContext[dotPathss[idx]];
      }
    }
    dotWalker.onEndAssigner = function(value){
      return function(context, idx, dotPathss, prevContext) {
        return prevContext[dotPathss[idx]] = value, prevContext[dotPathss[idx]];
      }
    }

dotWalker.assign = function (dotPath, value, context) {
  return dotWalker(dotPath, context, dotWalker.onEndAssigner(value), dotWalker.enRouteMapper);
}

dotWalker.get = function (dotPath, context) {
  return dotWalker(dotPath, context, dotWalker.onEnd, dotWalker.enRoute);
}
    dotWalker.enRouteMapper = function(context, idx, dotPathss, prevContext) {
        return context[dotPathss[idx]] = {}, context[dotPathss[idx]];
    }
    dotWalker.enRoute = function(context, idx, dotPathss, prevContext) {
      return dotWalker.objectHas(context, dotPathss[idx]) ? context[dotPathss[idx]] : undefined ;
    }
    dotWalker.onEach = function(context, idx, dotPaths, prevContext) {
      return context;
    }
    dotWalker.objectHas = function(obj, prop) {
      return obj && Object.prototype.hasOwnProperty.call(obj, prop);
    }
    dotWalker.isObject = function(value) {
      const type = typeof value
      return value != null && (type == 'object' || type == 'function')
    };

    return dotWalker;
  })();

  
  
  
  
   

   
  /////// EXAMPLES AND TESTS
  
  
  
  
  console.clear();
  var tcontext = {};
  var tmp;

  console.log('Testing pathFinder functionalities')
  console.log('_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _')
  
  tmp = dotWalker.pathFinder('some.path');
  console.log(
    'returns an array of paths, if no context is provided.', 
    (tmp.length !== 0)
  );
  
  tmp = dotWalker.pathFinder();
  console.log(
    'returns an empty set if no context or dotPaths is supplied.', 
    (tmp.length === 0)
  );
  
  tmp = dotWalker.pathFinder('some.path', { name: 'empty' });
  console.log(
    'returns a context object like { context:..., dotPaths:...}', 
    !!(tmp.context && tmp.dotPaths)
  );
  
  tmp = dotWalker.pathFinder('app:person.name');
  console.log(
    'returns undefined when no context is supplied.', 
    (tmp.context === undefined)
  );
  
  tmp = dotWalker.pathFinder('app:person.name');
  console.log(
    'returns undefined when no matching namespace exists.', 
    (tmp.context === undefined)
  );
  
  tmp = dotWalker.pathFinder('app:', { app: true });
  console.log(
    'returns a context based on namespace when one is specified in the path.', 
    (tmp.context === true)
  );
  
  tmp = dotWalker.pathFinder('app:', { app: true });
  console.log(
    'returns a context object with an empty dotPaths when none provided',
    'following a namespace.', 
    (tmp.context === true)
  );
  
  tmp = dotWalker.pathFinder('name', { name: 'John Doe' });
  console.log(
    'can use dotPathss for simple objects.', 
    (tmp.context.name === 'John Doe')
  );
  
  tmp = dotWalker.pathFinder('name', { ns: { root: { name: 'John Doe' } } });
  console.log(
    'will use the root namespace if none is specified.', 
    (tmp.context.name === 'John Doe')
  );
  
  tmp = dotWalker.pathFinder('app:name', { ns: { root: { name: 'John Doe' }, app: { name: 'App Name' } } });
  console.log(
    'will use namespaces to resolve values when supplied.', 
    (tmp.context.name === "App Name")
  );

  tmp = dotWalker.pathFinder('app:prop.that.does.not.exist', { ns: { root: { name: 'John Doe' }, app: { name: 'App Name' } } });
  console.log(
    'will return a valid namespace for paths that may not exist', 
    (tmp.context.name === "App Name")
  );
  
  console.log('- - - - - - - - - - - - - - - - - - - - - - - - - -')
  console.log('Testing dotWalker functionalities')
  console.log('_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _')
  
  tmp = dotWalker();
  console.log(
    'will return undefined, if no dotPaths or context is provided', 
    (tmp === undefined)
  );
  
  tmp = dotWalker('some.path');
  console.log(
    'will return undefined, if no context is provided', 
    (tmp === undefined)
  );
  
  tmp = dotWalker(undefined, {
    name: 'app name'
  });
  console.log(
    'will return context, if no dotPaths is provided', 
    (tmp.name === "app name")
  );
  
  tmp = dotWalker('', {
    name: 'app name'
  });
  console.log(
    'will return context, if dotPaths is an empty string', 
    (tmp.name === "app name")
  );
  
  tmp = dotWalker('name', { name: 'app name' });
  console.log(
    'With a simple object, without any supplied namespace, will ',
    'return a prop that matches the dotPaths', 
    (tmp === "app name")
  );

  var tmp = dotWalker('name.firstName', { name: {firstName: 'John Doe'}});
  console.log(
    'With a simple object, will retrieve a value of a nested dotPathss', 
    (tmp === 'John Doe')
  );
  
  // Namespaces 
  console.log('Namespaces...............');
  
  tmp = dotWalker('name:', { name: 'app name' });
  console.log(
    'With a simple object, where a namespace was provided ', 
    'and the dotPaths was undefined will return a prop value ',
    'that matches the namespace.', 
    (tmp === "app name")
  );
  
  tmp = dotWalker('name:firstName', {name: {firstName: 'John Doe'}
  }, dotWalker.onEnd, dotWalker.enRoute);
  console.log(
    'With a simple object, will retrieve a value of a dotPathss ',
    'when using namespaces', 
    (tmp === 'John Doe')
  );
  
  tmp = dotWalker('name:firstName.english.two',{ name: {firstName: { english: { two: 'John Doe'}}}});
  console.log(
    'With a simple object, will retrieve a value of a nested ', 
    'dotPaths, when using namespaces', 
    (tmp === "John Doe")
  );
  
  tmp = dotWalker('name:firstName.english.four', { name: {firstName: { english: { two: 'John Doe'}}}});
  console.log(
    'With a simple object, will return undefined for an invalid path ', 
    'dotPaths, when using namespaces', 
    ( tmp === undefined )
  );
  
  tmp = dotWalker('name:firstName.apple.four', { name: {firstName: { english: { two: 'John Doe'}}}});
  console.log(
    'With a simple object, will return undefined for an invalid path ', 
    'dotPaths, when using namespaces', 
    ( tmp === undefined )
  );

  // Assignments
  console.log('Simple Object Context Assignments...............');

  tcontext = {};
  tmp = dotWalker(
    'name', 
    tcontext,
    dotWalker.onEndAssigner('Sally Sue'), 
    dotWalker.enRouteMapper
  );
  console.log(
    'Can assign values to an object', 
    ( tmp === tcontext.name )
  );

  tcontext = {name: {}};
  tmp = dotWalker(
    'name.firstName', 
    tcontext,
    dotWalker.onEndAssigner('Sally'), 
    dotWalker.enRouteMapper
  );
  console.log(
    'Can assign a value using dotPathss', 
    ( tmp === tcontext.name.firstName )
  );

  tcontext = {name: {}};
  tmp = dotWalker(
    'person.name.firstName', 
    tcontext,
    dotWalker.onEndAssigner('Sally'), 
    dotWalker.enRouteMapper
  );
  console.log(
    'Can assign values using nested dotPathss, and it will fill in the missing links', 
    ( tmp === tcontext.person.name.firstName )
  );

  tcontext = {we:{}};
  tmp = dotWalker(
    'we:person.name.firstName', 
    tcontext,
    dotWalker.onEndAssigner('Sally'), 
    dotWalker.enRouteMapper
  );

  console.log(
    'Can assign values using nested dotPathss and namespaces, and it will fill in the missing links', 
    ( tcontext.we.person.name.firstName === tmp )
  );


 // Assignments
  console.log('Namespaced Context Assignments...............');

  tcontext = {ns:{}};
  tmp = dotWalker(
    'name', 
    tcontext,
    dotWalker.onEndAssigner('Sally Sue'), 
    dotWalker.enRouteMapper
  );
  console.log(
    'Can assign values to an object', 
    ( tmp === tcontext.name )
  );

  tcontext = {name: {}};
  tmp = dotWalker(
    'name.firstName', 
    tcontext,
    dotWalker.onEndAssigner('Sally'), 
    dotWalker.enRouteMapper
  );
  console.log(
    'Can assign a value using dotPathss', 
    ( tmp === tcontext.name.firstName )
  );

  tcontext = {name: {}};
  tmp = dotWalker(
    'person.name.firstName', 
    tcontext,
    dotWalker.onEndAssigner('Sally'), 
    dotWalker.enRouteMapper
  );
  console.log(
    'Can assign values using nested dotPathss, and it will fill in the missing links', 
    ( tmp === tcontext.person.name.firstName )
  );

  tcontext = {we:{}};
  tmp = dotWalker(
    'we:person.name.firstName', 
    tcontext,
    dotWalker.onEndAssigner('Sally'), 
    dotWalker.enRouteMapper
  );

  console.log(
    'Can assign values using nested dotPathss and namespaces, and it will fill in the missing links', 
    ( tcontext.we.person.name.firstName === tmp )
  );

  tcontext = undefined;
  tmp = dotWalker(
    'we:person.name.firstName', 
    tcontext,
    dotWalker.onEndAssigner('Sally'), 
    dotWalker.enRouteMapper
  );

  console.log(
    'returns undefined when no context is given', 
    ( tmp === undefined )
  );

  tcontext = {we:{}};
  tmp = dotWalker(
    'we:person.name.firstName', 
    tcontext,
    dotWalker.onEnd, 
    dotWalker.enRoute
  );

  console.log(
    'returns undefined when no value exists at path', 
    ( tmp === undefined )
  );


    tcontext = {we:{name: "John Doe"}};
  tmp = dotWalker.get(
    'we:name.firstName',
    tcontext
  );

  console.log(
    'follows a dotPath, and returns a value or undefined.', 
    ( tmp === undefined )
  );


  // Accessor Methods
  console.log('Accessors...............');

  tcontext = {we:{name:{}}};
  tmp = dotWalker.assign(
    'we:name.firstName',
    'John',
    tcontext
  );

  tmp = dotWalker.assign(
    'we:name.lastName',
    'Doe',
    tcontext
  );

  console.log(
    'assigns a value using onEndAssigner and enRouteMapper', 
    ( tcontext.we.name.firstName === "John" )
  );


  tmp = dotWalker.get(
    'we:name.firstName',
    tcontext
  );

  console.log(
    'follows a dotPath, and returns a value or undefined.', 
    ( tmp === "John" )
  );
  console.log('_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _')

\$\endgroup\$
  • 1
    \$\begingroup\$ Could I ask you why you put the tag functional-programming? \$\endgroup\$ – Mario Santini Apr 11 '17 at 18:07
  • \$\begingroup\$ Opps, i thought i chose 'Higher Order Functions', fixed, thanks. Although, technically I think if you squint hard enough, it does quality as functional, especially the objectWalker function. \$\endgroup\$ – r10y Apr 11 '17 at 18:12
  • \$\begingroup\$ I have rolled back your edits. Please do not change the code in your question after having received answers, so as not to invalidate them. Thank you. \$\endgroup\$ – Phrancis Apr 11 '17 at 22:45
2
\$\begingroup\$

First of all you originally add the tag functional-programming to your question.

Your code looks not so functional to me, for a couple for reasons.

First of all you design an object dotWalker, that have nothing to do with functional programming. You need just functions!

I know you apply the module path, to contain your code, but instead of keeping the private stuff private you expose all.

This is another point about your design.

I know that you ask just for the pathFinder method, but I found some issue in all the code, so I just point it out.

First of all the module name. As it is not a function, you should use a name that shown this.

You did good in the comment: DotWalker with the first letter as capital.

const dotWalker = function (dotPaths, context, end, enRoute, onEach){
  end = end || dotWalker.onEnd; 
  enRoute = enRoute || dotWalker.enRoute;
  var { context, dotPaths } = dotWalker.pathFinder( dotPaths || '', context);
  return objectWalker(dotPaths, context, end, enRoute, onEach);
}

If dotWalker is your module, you should define it as a plain object:

const DotWalker = {};

And then attack the functions you want to export outside the module:

DotWalker.init = function () {
    // ... the stuff goes here
}

You should not return the result of something else.

If you need to define an objectWalker then:

DotWalker.objectWalker = function (dotPaths, context, end, enRoute, onEach) {
    return theObjectWalker; // return what you need
}

You define 2 times this function:

dotWalker.assign = function (dotPath, value, context) {
  return dotWalker(dotPath, context, dotWalker.onEndAssigner(value), dotWalker.enRouteMapper);
}

So the last defined wins.

About your pathFinder method:

dotWalker.pathFinder = function(paths, c, namespace, pd, nd) {

What is wrong with proper names?

c: wolkerObject or originalObject

pd: pathDelimiter?

nd: namespaceDelimiter?

  pd = pd || '.';
  nd = nd || ':';

So, why don't put this constants as internals? I mean in your module closure, but outside the function, like:

const pathDelimiter = '.';
const namespaceDelimiter = ':';

If you need defaults values and then allow to change the values with configuration options:

const defaultPathDelimiter = '.';
const defaultNamespaceDelimiter = ':';

let pathDelimiter;
let namespaceDelimiter;

And in your constructor or initializer function:

pathDelimiter = opts['pathDelimiter'] || defaultPathDelimiter;
namespaceDelimiter = opts['namespaceDelimiter'] || defaultNamespaceDelimiter;

And then you just use pathDelimiter and namespaceDelimiter in your code.

Another point very important here is: avoid to override function parameter.

In complex application, if a function override the value of a parameter, it is often a drive to an hell situation in debugging.

Connecting this to functional programming, functions should take an input and provide some output and never change the input.

If you need for some reason, you should document on the API, or make a local copy of the value where you do your changes.

The following code appear a little confused to me.

  const lastChar = paths[paths.length - 1];
  const isNamespacedDotPath = paths.indexOf(nd) > -1;
  if ( !!dotWalker.objectHas(c, 'ns') ) {

The return of this method should be true, why you use !!?

    if ( lastChar === nd ) { // "label:" -> namespaced but no dotPaths
      namespace = c[paths.slice(0, -1)], paths = undefined;
    } 

I'll move this condition, after the check isNamespacedDotPath, as if this is fals, you don't have a chance that your last char is ':'.

And I think you should handle this in the else part too.

    if ( isNamespacedDotPath ) { // "label:some.sub.path" -> namespaced but with dotPaths
      paths = paths.split(nd), namespace = c.ns[paths[0]], paths = paths.slice(1)[0].split(pd);
    }
    if(!namespace){
      paths = paths.split(pd), namespace = c.selected || c.ns.root;
    }
  } else { /* Attempting to use namespaceses on simple object */
    if ( isNamespacedDotPath ) {
      paths = paths.split(nd);
      if( !c || !dotWalker.objectHas(c, paths[0])){
         namespace = undefined, paths = [];
      } else {
         namespace = c[paths[0]], paths = paths.slice(1).join('.').split(pd);
      }
    }
    if (typeof paths === 'string' && paths.indexOf(pd) > -1) {
      paths = paths.split(pd);
    }
    if ( lastChar === nd){
      paths = '';
    }
    namespace = namespace || c;
  }

I think the point here is to get the namespace as it is the starting point to walk through the path.

const contextArray = paths.split(ns);

if (contextArray.length !== 2 && paths.indexOf(ns) == -1) {
    // no namespace provided
} else if (contextArray.length !== 2) {
    // namespace only
} else { // namespace and paths

}

You could write functions that parse the namespace and the paths to break up the code and make it much readable.

  paths = paths[0] === '' || !paths ? undefined : [].concat(paths);

If paths is undefined, as it could be, this will throw an error.

You should first check if paths exists, then check if is an array.

  if (paths && patsh[0] === '') { // path is empty
      paths = undefined
  }

You just need this, as the other cases are already fine. No need to use concat.

  return !c ? paths : { dotPaths: paths, context: namespace };
}

Another point here is that you return from the same function 4 different type:

  • array
  • object
  • string
  • undefined

You should always be consistent, so I would return an object in any case. If I didn't find something the object will be empty, otherwise will be fit with the expected values.

But you could have also an object or undefined. This is ok too.

But have a function that could return anything is not so good.

dotWalker.onEach = function(context, idx, dotPaths, prevContext) {
  return context;
}

Ok, don't know why you need this.

dotWalker.objectHas = function(obj, prop) {
  return obj && Object.prototype.hasOwnProperty.call(obj, prop);
}

I think this should be an internal function, and I don't like your implementation. Instead:

function objectExistsAndHasProperty (object, property) {
    return (object || {}).hasOwnProperty(property);
}

Hope my considerations help you to refactor your module.

\$\endgroup\$
  • \$\begingroup\$ thanks for all the feedback, can i ask what you meant by "Another point very important here is: avoid to override function parameter. In complex application, if a function override the value of a parameter, it is often a drive to an hell situation in debugging." and what specifically in my code your where referring to. \$\endgroup\$ – r10y Apr 11 '17 at 21:42
  • \$\begingroup\$ Thanks to your feedback, i cleaned things up a little, scoping and api mainly. I also refactored pathFinder to work on arrays too. So now the objectWalker can parse dotPaths for Context Objects, Objects and arrays! thanks. \$\endgroup\$ – r10y Apr 11 '17 at 22:29
  • \$\begingroup\$ @r10y I mean, you override your function parameters often. For example paths in pathFinder, is even changed in type. This is not a good practice, even the language allow you to do this as javascript is dynamic and didn't complains. Instead you should create a local variable, and change this. Hope this make it clear. If not, feel free to ask. \$\endgroup\$ – Mario Santini Apr 12 '17 at 19:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.