DEV Community

Kurapati Mahesh
Kurapati Mahesh

Posted on

Find path in given object

// Problem:

/*
- Write method findPath
- Should take two params:
    - object
    - keys separated by dots as string
- Return value if it exists at that path inside the object, else return undefined
*/

var obj = {
    a: {
        b: {
            c: 12,
            j: false
        },
        k: null
    }
};
Enter fullscreen mode Exit fullscreen mode
// Solution:

const findPath = (object, path) => {
    let i=1;
    const pathList = path.split('.');
    let obj = object[pathList[0]];
    while(i<pathList.length) {
        if(typeof obj === 'object') {
            obj = obj[pathList[i]];
            i++;
        } else {
            return undefined;
        }
    }
    return obj;
};
Enter fullscreen mode Exit fullscreen mode
// cases
console.log(findPath(obj, 'a.b.c')); // 12
console.log(findPath(obj, 'a.b')); // {c: 12, j: false}
console.log(findPath(obj, 'a.b.d')); // undefined
console.log(findPath(obj, 'a.c')); // undefined
console.log(findPath(obj, 'a.b.c.d')); // undefined
console.log(findPath(obj, 'a.b.c.d.e')); // undefined
console.log(findPath(obj, 'a.b.j')); //false
console.log(findPath(obj, 'a.b.j.k')); //undefined
console.log(findPath(obj, 'a.k')); //null
Enter fullscreen mode Exit fullscreen mode

thanks

Top comments (26)

Collapse
 
loucyx profile image
Lou Cyx

The problem with this is that if you do something like:

const obj = {
  ".": "This is valid JS"
};
Enter fullscreen mode Exit fullscreen mode

And then you try to get it:

findPath(obj, ".");
Enter fullscreen mode Exit fullscreen mode

You get undefined, because you're using "." to split the path.

Collapse
 
urstrulyvishwak profile image
Kurapati Mahesh

This could be an impossible case as no one could assign key as dot. Anyhow, my solution is to cover the use cases mentioned.

Collapse
 
loucyx profile image
Lou Cyx
// it is very possible
obj["."] = "new value";
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
baenencalin profile image
Calin Baenen

You can also directly define "." in the object declaration as well.

Thread Thread
 
loucyx profile image
Lou Cyx

Yup, my first example had that 😅

Collapse
 
baenencalin profile image
Calin Baenen • Edited

I think you forgot you can format objects like JSON (JavaScript Object Notation):

let my_obj = {
    "foo": "bar",
    ".":   "baz"
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
urstrulyvishwak profile image
Kurapati Mahesh

I was referring to real world scenario. No one uses any object key as dot alone.

Usually, programmers give readable key name based on their requirement.

Everything has its corner cases, I am generalizing concept to find the path. It is not that I need to cover all worst cases.

Hope you guys get it. Also, it would be better if you guys provide solution for the case you are mentioning. That might worth for the people came across this article.

Thank you.

Thread Thread
 
fjones profile image
FJones

The simplest solution would be to do away with split and instead consider a token-based approach:

"." refers to a field called "." on the root, whereas "foo.." refers to a field called "." on the nested object at root-key "foo". And then "foo...bar" refers to a foo->.->bar path.

However, this also causes some ambiguity: foo.....bar could be either foo->.->.->bar or foo->...->bar.

A generalized solution would then use escape sequences. e.g. \. for a dot literal, \\ for a backslash literal, but that naturally makes parsing the path ever so more complex.

Thread Thread
 
baenencalin profile image
Calin Baenen

I like ðis approach.
Sure it requires some more complexity and probably takes longer (but not too much) than .split(char_seq) but it gets ðe job done and better.

However, this also causes some ambiguity: foo.....bar could be either foo->.->.->bar or foo->...->bar.

Ðis could be solved by checking ðe more specific case (foo->.->.->ba) first and if ðat fails try ðe simpler one(s).
Of course it won't be perfect but it can at least try to figure out intentions.

Thread Thread
 
loucyx profile image
Lou Cyx • Edited

If you use an object to represent directories and files with their respective data, you could use "." to represent the current directory. Something like:

{
  ".": {
    "fullPath": "/home/user/example/",
    type: "directory"
  },
  "..": {
    "fullPath": "/home/user/",
    "type": "directory"
  },
  "aFile.txt": {
    "fullPath": "/home/user/example/aFile.txt",
    "type": "text/plain"
  }
}
Enter fullscreen mode Exit fullscreen mode

The idea is that you should support all valid keys, and that includes keys with dots. All the above will fail because they have dots on them. I would just not have a function like this, and instead use optional chaining, to be honest:

// Instead of this:
console.log(findPath(obj, 'a.b.d'));

// I would just do this:
console.log(obj?.a?.b?.d);
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
jonrandy profile image
Jon Randy 🎖️

A Symbol is a valid key 😂

Thread Thread
 
loucyx profile image
Lou Cyx • Edited

IKR ... the only way I can think to implement that with a function like this would be using template literals and doing something like path.to.${Symbol("example")} .... and is way more complex than just using optional chaining (path?.to?.[Symbol("example")]) 😅

Thread Thread
 
jonrandy profile image
Jon Randy 🎖️

Symbols in template strings = TypeError: can't convert symbol to string :)

Thread Thread
 
loucyx profile image
Lou Cyx • Edited

I was talking about doing something like this:

const templateLogger = (strings, ...values) => {
    console.log({ strings, values });
};

templateLogger`hello.${Symbol("there")}.${Symbol("Jon")}`;
Enter fullscreen mode Exit fullscreen mode

That logs:

{
    strings: ["hello.", ".", ""],
    values: [Symbol("there"), Symbol("Jon")]
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
baenencalin profile image
Calin Baenen

Ðat's why ðey have a .toString() meþod, I believe.

Collapse
 
lexlohr profile image
Alex Lohr

Recursion makes for a simpler solution:

const findPath = (obj, path) => {
  const [item, nextPath] = /([^\.]+)\.(.*)/.exec(path)?.slice(1, 3) || [path];
  return nextPath ? findPath(obj?.[item], nextPath) : obj?.[item];
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
baenencalin profile image
Calin Baenen

As Frank Wisniewski suggests, try Optional Chaning.
It works on more than just object properties as well. It also lets you optionally call functions.

Collapse
 
urstrulyvishwak profile image
Kurapati Mahesh

Yes. Optional chaining is latest feature and might not be used (or enabled) everywhere. If someone is using then cool otherwise we need to follow traditional way.

Collapse
 
loucyx profile image
Lou Cyx

Unless you're planning on supporting unsupported browsers such as Internet Explorer, then optional chaining is supported pretty much everywhere ... and even if it isn't, you can use Babel or TypeScript to compile your modern code to support unsupported browsers.

Collapse
 
jonrandy profile image
Jon Randy 🎖️

This confused me initially because of the function name - I thought we were going to find a path to a property with a given value, but we're actually doing the opposite. A better name might be getPropertyWithPath

Collapse
 
urstrulyvishwak profile image
Kurapati Mahesh

findPath similar to getPropertyPath. I hope people would get it as I am referring to object.

Collapse
 
frankwisniewski profile image
Frank Wisniewski

Does that really make sense?

console.log(
  obj.a?.b?.c?.d
)
// or
console.log(
  obj.a?.b
)
Enter fullscreen mode Exit fullscreen mode

gives the same result

Collapse
 
baenencalin profile image
Calin Baenen

True, but I þink ðis is more for backwards compatibility.
But yes, newer code should probably use Optional Chaining.

Collapse
 
urstrulyvishwak profile image
Kurapati Mahesh

Yes. Optional Chaining is the best and more readable.

Collapse
 
jonrandy profile image
Jon Randy 🎖️

But this isn't really doing the same thing? It's not dynamic

Collapse
 
frankwisniewski profile image
Frank Wisniewski

I don't understand what you mean by that..