0

It's very common to need to get a value from an object in javascript, and the trouble is when I'm trying to get a value or see if there is a value for some nested object key, I have to do things like this

// access deeply nested values...
obj['key1'] &&
obj['key1']['key2'] &&
obj['key1']['key2']['key3'] &&
obj['key1']['key2']['key3']['key4']

There are many solutions online for this but all I've found use some methods and concepts I don't understand as a beginner, like map/reduce methods or hasOwnProperty method or other methods.

Is it possible to do this with a simple for loop of the kind:

function getValueAt(searchPath) {
  for (i = 0; i < searchPath.length; i++) {
    /* implementation */
  }
  return
}

Arrays are javascript objects, and this helper function should support searching array objects and non-array objects.

The return result should be undefined if searchPath doesn't exist, and should be the value if it does exist.

How the getValueAt function would be called: getValueAt(obj['key1']['key2']['key3']['key4'])

So the function should check if obj exists, then check if each passed property is not undefined, one at a time, then finally get the value if it passes all those checks, otherwise return undefined

Gallaxhar
  • 976
  • 1
  • 15
  • 28
  • Are you wanting someone to write this (crazy) method for you? I don't see any actual questions here? I'm pretty sure you have yourself an [XY problem here.](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – Liam Jun 19 '18 at 15:33
  • This isn't a method, I'm trying to avoid methods altogether. I'm trying to access a nested value from an object without using methods. A helper function template is just the way I thought of first. Also the question is in the title. – Gallaxhar Jun 19 '18 at 15:35
  • Possible duplicate of [Test for existence of nested JavaScript object key](https://stackoverflow.com/questions/2631001/test-for-existence-of-nested-javascript-object-key) – Isaac Jun 19 '18 at 15:37
  • @Isaac, that solution uses .hasOwnProperty method, my question specifically is about not using methods – Gallaxhar Jun 19 '18 at 15:38
  • 1
    Why do you "not want to use methods"? That's a bit bizarre... you realise your `getValueAt` **is** a method? – Liam Jun 19 '18 at 15:39
  • Short answer: you definitely need `.hasOwnProperty`. You are planning to loop thru all keys of an object, the idea is correct. However every object will have some extra keys that you may want to avoid – Isaac Jun 19 '18 at 15:40
  • You can pass `searchPath` as an array. – Eddie Jun 19 '18 at 15:41
  • I highly recommend learning and utilizing lodash for this. lodash's `get` is exactly what you want. – River Tam Jun 19 '18 at 15:46
  • @River Tam I was using sugarjs for this, it was the only reason I loaded that whole library in. That's what I'm trying to avoid here. – Gallaxhar Jun 19 '18 at 15:47
  • Are you trying to avoid large bundle sizes? Lodash ships their methods independently so if you're using npm, you can just say `require('lodash/fp/get')` and just get that one method. You should really clarify why you're trying to "avoid methods" or libraries or whatnot, and you should really clarify that in the question. – River Tam Jun 19 '18 at 15:48

2 Answers2

1

You can pass searchPath as an array. Loop thru the array and assign the value if exist. If not return null

function getValueAt(object, searchPath) {
  for (var i = 0; i < searchPath.length; i++) {
    if (object[searchPath[i]] !== undefined) object = object[searchPath[i]]; //If exist, assaign the value on variable o
    else return null;                                          //If does not exist, return null
  }
  return object;
}

var obj = {'k1': {'k2': {'k3': 'Test Value'}}};

var result = getValueAt(obj, ['k1', 'k2', 'k3']);

console.log(result);

You can also use reduce instead of for loop.

function getValueAt(object, searchPath) {
  return searchPath.reduce((c, v) => c[v] ? c[v] : null, object);
}

var obj = {'k1': {'k2': {'k3': 'Test Value'}}};

var result = getValueAt(obj, ['k1', 'k2', 'k3']);

console.log(result);
Eddie
  • 26,593
  • 6
  • 36
  • 58
  • https://jsfiddle.net/cd9rzt28/ I verified that this works with array objects in the nesting as well, thanks. The solution without `reduce` is perfect. – Gallaxhar Jun 19 '18 at 15:59
  • could you modify line 3 to be `if (object[searchPath[i]] !== undefined)` the code fails if the value it's searching for is false, which it often is set to in an object – Gallaxhar Jun 19 '18 at 17:05
  • 1
    Here's a fiddle link to corrected code example, with a bonus 'countIn' function that I was only able to build because of your answer :-) https://jsfiddle.net/jqys57xp/ – Gallaxhar Jun 19 '18 at 17:06
  • @Gallaxhar Good Point :) – Eddie Jun 19 '18 at 17:06
0

This avoids using native methods by using recursion. The implementaion is pretty simple:

let obj = { key1: { key2: { key3: { key4: true } } }};

function getValueAt(o, keys, index = 0) {
    const val = o[keys[index]];
    return val === undefined || index + 1 === keys.length ? 
        val : getValueAt(val, keys, ++index);
}

console.log(getValueAt(obj, ['key1', 'key2', 'key3', 'key4'])); // true
console.log(getValueAt(obj, ['key1', 'key2', 'key3', 'key4', 'key5'])); // undefined

I'm not entirely certain why you want to "avoid methods" as you state, but this circumvents the issue by only accessing one key deeper on each recursion.

It's also important to note that it's not possible to pass the object into the function as you suggested, like getValueAt(obj['key1']['key2']['key3']['key4']), because the js interpreter would evaluate it and it would throw an undefined error if the keys don't already exists...which kinda defeats the purpose of what you're trying to achieve.

jonny
  • 3,022
  • 1
  • 17
  • 30
  • it uses a .shift() method though, just replacing one method with another – Gallaxhar Jun 19 '18 at 16:02
  • @Gallaxhar I've updated the answer to avoid using methods. Why is it you want to avoid methods may I ask? – jonny Jun 19 '18 at 16:11
  • because it's more to learn and keep track of, trying to keep track of all these methods and es6 syntax sugar business. I have my own personal theory that I can do 99.999% of all of that in plain js with for loops for iteration and properties like array.length, and while full time js developers may love their sugar it just makes the language harder for newbies, more confusing, harder to read and comprehend. I'd love to work in clojurescript instead of js in the first place for the sanity in language design but there's not as much tooling I need (building single page applications). – Gallaxhar Jun 19 '18 at 16:13
  • ...So I've limited myself to a subset of javascript instead. – Gallaxhar Jun 19 '18 at 16:14
  • I 100% understand that sentiment and I think it's a sound one, but it's also ill-informed. Constructs like iterators exist because they eliminate index out of bound errors, they prevent unnecessary memory assignments, and they can often be *easier* to reason about than primitive control flow constructs when doing anything more complex than a single loop. – jonny Jun 19 '18 at 16:18
  • for loops are iterators, they're just simpler ones than map and filter, at least to me, in javascript. in clojurescript that might be a different story, not sure. – Gallaxhar Jun 19 '18 at 16:21
  • also it's easier to understand higher abstractions in general when you are familiar with the terminology, each method is a helper function with syntax sugar shortcut to do an idea or concept - once you memorized those ideas or concepts then great, but if you don't know them then it's just another barrier. if the language has to be 10x more full of syntax just to prevent the occasional 'index out of bound error' or reduce 30% of the characters on the screen, then it's done badly, and I'd be in the camp that just patches the errors instead of make the language perl. – Gallaxhar Jun 19 '18 at 16:26