1

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.

5 Answers 5

1

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)

Sign up to request clarification or add additional context in comments.

3 Comments

I don't understand this answer - I don't have 'foo.bar.star.guitar' as a string. I don't have that info. All I know is whether the object resolved or it threw an error "cannot read property x of undefined'.
@AlexanderMills right - so you have some arbitrary path string, and you need to extract the value corresponding to that path from input object? The snippet above is intended to illustrate how this can be done in general terms. Also, something to be aware of is that the path foo.bar.star.guitar is actually invalid for the object foo in your OP. Is there a format that you would prefer this answer to be presented in?
Your answer is how to resolve foo.bar.star.guitar from 'foo.bar.star.guitar'. But the question is about how to infer 'foo.bar.star.guitar' from foo.bar.star.guitar, for generating error messages for improper object access.
1
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?

1 Comment

sure if you can create a good regex to extract the string from the arrow function, that'd be useful.
1

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

Comments

1

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;
                       ^

Comments

1

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.

5 Comments

this would work, but I want to log the full path, not just the point where it failed. Proxy objects are cool tho.
Not sure I get you - it does give the full path
You have 3 console.log output lines, they arent showing the full path, only shows where it failed. For example if it failed at bar it would say could not resolve foo.bar
Ah yes I get you now! Hmm, needs more thought.
Any new ideas on this front? thx. This answer is fine, just leave it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.