8

I have this JSON

var myJSON = {
    "a": {
        "b": {
            "c": {
                "Foo": "Bar"
            }
        }
    }
}

I also have this array:

var path = ["a", "b", "c", "foo"]

How can I use the path to get Bar?

Bob van Luijt
  • 7,153
  • 12
  • 58
  • 101
  • 1
    `var x = myJSON; path.each(function(key, val) { x = x[key]; }` would be on (hopefully working) dirty way of doing it. – Marc B Sep 03 '15 at 19:34

4 Answers4

19

Check out Array.prototype.reduce(). This will start at myJSON and walk down through each nested property name defined in path.

var myJSON = {
  "a": {
    "b": {
      "c": {
        "Foo": "Bar"
      }
    }
  }
};

var path = ["a", "b", "c", "Foo"]; // capitalized Foo for you...

var val = path.reduce((o, n) => o[n], myJSON);

console.log("val: %o", val);
canon
  • 40,609
  • 10
  • 73
  • 97
  • 2
    With an additional check for undefined values, this is the solution I usually go for, concise and straight-forward. Since I've come back to this answer numerous times now, I would add a bounty as a reward if this question wasn't closed as a duplicate. – Etheryte Jan 18 '16 at 17:21
  • @nit the dupe target's [accepted answer](http://stackoverflow.com/a/28682850/621962) suggests `eval()`... :/ – canon Nov 22 '16 at 16:25
  • Your answer is clearly the better one, but I'm not sure whether to vote to reopen or flag because the dupe flags go to another question as well. – Etheryte Nov 22 '16 at 20:22
  • Those dupe chains are interesting beasts... – canon Nov 22 '16 at 20:23
6

Have a variable that stores the value of the object, then iterate through the path accessing the next property in that variable until the next property is undefined, at which point the variable holds the end property.

var val = myJSON;

while (path.length) {
    val = val[path.shift()];
}

console.log(val);
Elliot Bonneville
  • 51,872
  • 23
  • 96
  • 123
  • shouldn't you check path[0] instead of val[path[0]]? – Daemedeor Sep 03 '15 at 19:38
  • note that shifting is probably O(N) instead of O(1), though it probably depends on how its implemented – twinlakes Sep 03 '15 at 19:41
  • 2
    `path.reverse()` before going into the `while` (pun intended :D) and then `pop()`the hell outta it! – axelduch Sep 03 '15 at 19:46
  • @canon, I get the opposite results on [this test](http://jsperf.com/reduce-vs-shift/5), with shift coming in first, pop second, and reduce last?? – KyleMit Sep 04 '15 at 14:06
  • 1
    @KyleMit that's because you've corrupted the test by caching `path`. So, after either the `shift()` or `pop()` test runs once, there's nothing left in `path` to process for the remaining iterations. `path.length` will be `0` from that point on, thereby skipping the `while()` loops. That said, `pop()` may be faster even with a legitimate test. – canon Sep 04 '15 at 14:21
  • 1
    @canon, ok, here's a [fixed version](http://jsperf.com/reduce-vs-shift/5), which is now ordered pop, reduce, shift – KyleMit Sep 04 '15 at 14:24
  • @KyleMit looks good. – canon Sep 04 '15 at 14:25
4
function access(json, path, i) {
  if (i === path.length)
    return json;
  return access(json[path[i]], path, i + 1);
}

to use:

access(myJSON, path, 0)
twinlakes
  • 9,438
  • 6
  • 31
  • 42
  • 1
    Completely unnecessary use of recursion. Iterative approaches are generally more performant and the stack trace is also more convenient to analyse in the event of a bug. – Etheryte Sep 03 '15 at 19:40
  • Except that recursion is provable by induction and therefore easier to debug – twinlakes Sep 03 '15 at 19:44
  • It is indeed slower, but it's an interesting approach nonetheless. Recursion is like sugar for the mind :) – Domino Sep 03 '15 at 19:44
  • @twinlakes That has nothing to do with ease of debugging. – Etheryte Sep 03 '15 at 19:45
  • @Nit arguing that something is better for stack trace has only to do with debugging. Also the slowdown is asymptotically negligible. – twinlakes Sep 03 '15 at 19:46
  • 1
    I'd say tree traversal is one of the most common cases for recursion, this is the textbook answer for OP's textbook question. It's also easier to understand than the iterative approach. – Parris Varney Sep 03 '15 at 19:49
  • @ParrisVarney That opinion seems to be the minority here, as both iterative approaches have more votes. – Etheryte Sep 03 '15 at 19:56
  • @Nit reductions are also a functional solution, not an iterative solution... – twinlakes Sep 03 '15 at 19:57
  • @twinlakes while it involves function calls, reduction is applied iteratively. – canon Sep 03 '15 at 21:04
  • @canon isn't that implementation specific? In their response, there is no sequence of steps--only one step--similar to saying that Haskell isn't functional because it's written in C. – twinlakes Sep 03 '15 at 21:21
  • @twinlakes I'm referring to `Array.prototype.reduce()` as defined by the [ECMAScript 5.1 spec](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.21), because that's what bears upon this question. So, unless you're talking about reduction _in general_, and not as it pertains to this question or its answers... yes, it's applied iteratively. – canon Sep 03 '15 at 21:30
  • This is such a basic implementation that someone has got to write a library for it and put it on npm – xdevs23 Jan 07 '19 at 14:11
0
var myJSON = {
    "a": {
        "b": {
            "c": {
                "Foo": "Bar"
            }
        }
    }
}

var path = ["a", "b", "c", "Foo"]


var findViaPath = function (path, object) {
    var current = object;

    path.forEach(function(e) { 
        if (current && e in current) {
            current = current[e];
        } else {
            throw new Error("Can not find key " + e)
        }
    })

    return current;
}

findViaPath(path, myJSON);
canon
  • 40,609
  • 10
  • 73
  • 97
djaszczurowski
  • 4,385
  • 1
  • 18
  • 28
  • @canon, thanks for offering an explanation. It's helpful to the Question OP, answer OP, and any future travelers who are looking to figure out which solution to implement. Also, other than perhaps being less performant - I'm not sure it's over engineered. Sure it wraps the check in a function and provides some error checking, but that's not the worst thing in the world. It is relevant that data that looks like `Foo: false` will throw an error with `current(e)` = `current('Foo')` = `false` – KyleMit Sep 04 '15 at 15:01
  • @canon, it looks like `forEach` and `reduce` are both worse than just a nieve `for` loop https://jsperf.com/array-reduce-vs-foreach/9 - but they're all basically doing the same thing. The (now fixed) error checking in this solution is something that could be added to any of the other solutions to make them more robust. – KyleMit Sep 04 '15 at 15:17
  • Here's [another test](http://jsperf.com/reduce-vs-shift/7). I think if we're going to add forEach to the jsPerf than we have to compare apples to apples and not include the additional error checking or function calls (both of which I think are actually good things for clarity. What's this weird loop do? Oh, it's inside a function that tells me! In terms of error-checking, a full path would be nice. – KyleMit Sep 04 '15 at 15:39