Search recursively for value in object by property name

Your code has a few errors:

  • You're recursively calling util.findVal but not returning the result of the call. Code should be return util.findVal(...)
  • You're not passing the attribute name key to the recursive call
  • You're not handling the possibility of a reference loop
  • If an object contains a key and also a sub-object that contains the key which value is returned is random (depends on the sequence in which the keys are analyzed)

The third problem is what can cause infinite recursion, for example:

var obj1 = {}, obj2 = {};
obj1.x = obj2; obj2.y = obj1;

if you just keep looking recursively searching in obj1 or obj2 could lead to infinite recursion.

Unfortunately for reasons not clear to me in Javascript is impossible to know the object "identity"... (what Python id(x) does) you can only compare an object to another. This means that to know if an object has already been seen in the past you need a linear scan with known objects.

ES6 added the possibility to check object identity with Set and Map where objects can be used as keys. This allows for faster (sub-linear) search times.

A search solution that runs in depth order could be for example:

function findVal(obj, key) {
    var seen = new Set, active = [obj];
    while (active.length) {
        var new_active = [], found = [];
        for (var i=0; i<active.length; i++) {
            Object.keys(active[i]).forEach(function(k){
                var x = active[i][k];
                if (k === key) {
                    found.push(x);
                } else if (x && typeof x === "object" &&
                           !seen.has(x)) {
                    seen.add(x);
                    new_active.push(x);
                }
            });
        }
        if (found.length) return found;
        active = new_active;
    }
    return null;
}

given an object and an attribute name, returns all the values found with that name at the first depth they are found (there can be more than one value: for example when searching {x:{z:1}, y:{z:2}} for the key "z" two values are at the same depth).

The function also correctly handles self-referencing structures avoiding infinite search.


Don't write your own utility if you can avoid it.

Use something like jsonpath

Some examples of supported syntax:

JSONPath                   Description
$.store.book[*].author      The authors of all books in the store
$..author                   All authors
$.store.*                   All things in store, which are some books and a red bicycle
$.store..price              The price of everything in the store
$..book[2]                  The third book
$..book[(@.length-1)]       The last book via script subscript
$..book[-1:]                The last book via slice
$..book[0,1]                The first two books via subscript union
$..book[:2]             The first two books via subscript array slice
$..book[?(@.isbn)]          Filter all books with isbn number    

You could use Object.keys and iterate with Array#some.

function findVal(object, key) {
    var value;
    Object.keys(object).some(function(k) {
        if (k === key) {
            value = object[k];
            return true;
        }
        if (object[k] && typeof object[k] === 'object') {
            value = findVal(object[k], key);
            return value !== undefined;
        }
    });
    return value;
}

var object =  { photo: { progress: 20 }};
console.log(findVal(object, 'progress'));