Alexander Mills
Alexander Mills

Reputation: 100080

Get string representation of object path

Say I have a situation like this:

const foo = {
   bar: {
     star: {
       guitar: 'geetar'
      }
   }
}

and then I have:

const stew = {
   moo: () => foo.bar.star.guitar
}

then I call moo in the next tick of the event loop:

process.nextTick(function(){
   const guitar = stew.moo();
});

my question is - is there any way/trick to get the string representation of the path: "foo.bar.star.guitar"?

I could replace the code with a string:

    const stew = {
       moo: () => 'foo.bar.star.guitar'
    }

but I am looking to find a way to get a string representation. One important detail is that I want to generate a useful error message - if someone puts in an object path that doesn't exist. That is the whole purpose of getting a string representation - for a useful error message.

Upvotes: 1

Views: 231

Answers (5)

Lauren
Lauren

Reputation: 1500

If you are able to control the root variables that people can refer to, you could wrap them in a Proxy object which tracks the property access all the way down, like so:

function callStackTracker(propsSoFar) {
    return {
        get: function(obj, prop) {
            if (prop in obj) {
                return typeof obj[prop] === "object" ?
                    new Proxy(obj[prop], callStackTracker(propsSoFar.concat(prop))) :
                    obj[prop];
            } else {
                throw new Error("Couldn't resolve: " + propsSoFar.concat(prop).join('.'));
            }
        }
    };
};

let foo = {
   bar: {
     star: {
       guitar: 'geetar'
      }
   }
}

foo = new Proxy(foo, callStackTracker(['foo']));

console.log(foo.bar.star.guitar); // "geetar"
console.log(foo.bar.star.clarinet); // Error: Couldn't resolve: foo.bar.star.clarinet
console.log(foo.bar.boat); // Error: Couldn't resolve: foo.bar.boat

I am then throwing this resolved path as an error in this example, but you could do whatever you like with it.

Upvotes: 1

rlemon
rlemon

Reputation: 17666

based on your comments about this being for useful error messages, I believe you can get away with Error.captureStackTrace

const er = {};
Error.captureStackTrace(er);

const foo = {
    bar: {
        star: {
            guitar: "geetar"
        }
    }
};

const a = foo.bar.nope.guitar;

console.log(er.stack);

which logs

const a = foo.bar.nope.guitar;
                       ^

Upvotes: 1

Alexander Mills
Alexander Mills

Reputation: 100080

This is for a library, so I won't have any other information besides:

const stew = {
   moo: () => foo.bar.star.guitar
}

so one thing I can do is:

let guitar;
try{
  guitar = stew.moo();
}
catch(err){
  console.error('Could not get value from:', stew.moo.toString());
}

aka, just log the string representation of the function. This is good enough information for the user to have to debug the problem.

For a full demo see: https://gist.github.com/ORESoftware/5a1626037cb8ba568cdffa69374eac1d

Upvotes: 1

Lauren
Lauren

Reputation: 1500

const stew = {
   moo: () => foo.bar.star.guitar
}

stew.moo.toString().match(/([a-z0-9_$]+\.?)+/)[0]
// returns "foo.bar.star.guitar"

This somewhat depends on people always using the same kind of function. Also I just used a short-hand for valid function name - the actual regex would be much longer.. What characters are valid for JavaScript variable names?

Upvotes: 1

Dacre Denny
Dacre Denny

Reputation: 30370

One approach could be to use a reducer to extract the value from a supplied path. For a path such as bar.star.guitar, you could extract the value geetar from object:

const foo = {
   bar: {
     star: {
       guitar: 'geetar'
      }
   }
}

via the following:

const path = 'bar.star.guitar'; // Remove "foo" from your path

const foo = {
   bar: {
     star: {
       guitar: 'geetar'
      }
   }
}

const value = path
.split('.') // Break path into parts, splitting by '.'
.reduce((currentObject, pathPart) => {

  // Iterate through each part of the path in order, and incrementally 
  // extract and return corresponding value of currentObject if it 
  // exists. Repeat this for each path part until we find value from 
  // input object at end of the path
  if(currentObject) {
    currentObject = currentObject[ pathPart ]
  }
  
  return currentObject
}, foo);

console.log('path: ', path, 'value:', value)

The idea here is to iterate in order through each "part" (ie string separated by '.') of the input path, and incrementally extract and return the value corresponding to the part (key) of the input object being processed. We incrementally continue this process until the end of the path is reached, at which point we arrive at the desired value (if it exists)

Upvotes: 1

Related Questions