29

I'm building an utility function that should search for a property name and return its value once it is found. It should do this recursively:

// Function
util.findVal = (object, propName) => {
  for (let key in object) {
    if (key === propName) {
      console.log(propName)
      console.log(object[key])
      return object[key]
    } else {
      util.findVal(object[key], propName)
    }
  }
}

// Input
object: {
  photo: {
    progress: 20
  }
}

// Usage
util.findVal(object, 'progress')

However the console log goes forever and the browser crashes. What am I doing wrong?

EDIT:

This is how I'm calling the function:

// Input

item: {
  photo: {
    file: {},
    progress: 20
  }
}

this.findProgress(item)

methods: {
  findProgress (item) {
    return util.findVal(item, this.propName)
  }
}
alex
  • 7,111
  • 15
  • 50
  • 77

13 Answers13

46

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'));
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
8

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.

AndreCunha
  • 183
  • 8
6502
  • 112,025
  • 15
  • 165
  • 265
  • When trying to edit this answer to fix the missing closing parenthesis on the example's "else if", Stack Overflow's restriction on minimum amount of changed characters striked in forcing some unrelated changes to be made in order to submit the edit for review. I hope it does not get rejected for that. – AndreCunha Jul 27 '19 at 13:19
4

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    
Andrei Gec
  • 134
  • 2
  • 5
2

try changing else statement like this

return util.findVal(object[key],propName)

Sagar
  • 538
  • 3
  • 9
2

I know this is an old post, but I found it helpful to answer a problem I had with recursively finding a value by it's key. I further developed the answer given by Nina Scholz, and came up with the following. It should be quicker as it is not creating an array of all of the keys each time it is recursively invoked. Also, this will explicitly return false if the key is not found.

function findVal(obj, keyToFind) {
  if (obj[keyToFind]) return obj[keyToFind];

  for (let key in obj) {
    if (typeof obj[key] === 'object') {
      const value = findVal(obj[key], keyToFind);
      if (value) return value;
    }
  }
  return false;
}

var object =  { photo: { progress: 20 }};
console.log(findVal(object, 'progress'));
Lee Morris
  • 33
  • 6
1

Think about it if there is no key found.

I think you could do something like this instead of search

return object[propName] || null 

In your code there was a breakpoint missing, I guess you are trying to search inside the whole object not just the directly related attributes so here is an edit for you code

EDIT:

util.findVal = (object, propName) =>{
 if(!!object[propName]){
   return object[propName]
 }else{
   for (let key in object) {
     if(typeof object[key]=="object"){
      return util.findVal(object[key], propName)
     }else{
      return null
     }
  }
 }
}
Ryad Boubaker
  • 1,491
  • 11
  • 16
1

I think you are saying that you want to look for the property name anywhere recursively within the objects tree of properties and sub-properties. If so, here is how I would approach this:

var object1 = _getInstance(); // somehow we get an object
var pname = 'PropNameA';

var findPropertyAnywhere = function (obj, name) {
    var value = obj[name];
    if (typeof value != 'undefined') {
        return value;
    }
    foreach(var key in obj) {
        var v2 = findPropertyAnywhere(obj[key], name);
        if (typeof v2 != 'undefined') {
            return v2;
        }
    }
    return null;
}
findPropertyAnywhere(object1, pname);
Glenn Ferrie
  • 10,290
  • 3
  • 42
  • 73
  • The test `typeof value != 'undefined'` would likely be better as `obj.hasOwnProperty(name)` since the property may exist and have the value *undefined*. – RobG Nov 15 '16 at 07:27
1

Found this question in the realm of needing a general solution to check if an object contains a specific value anywhere in its hierarchy (regardless of the key), which can include arrays of course. So the following does not answer OPs question directly or improve upon other solutions but it might help others looking for the same thing I did and finding this post:

function hasValue(object, value) {
    return Object.values(object).some(function(val) {
        if (val === value) {
            return true;
        }
        if (val && typeof val === 'object') {
            return hasValue(val, value);
        }
        if (val && val.isArray()) {
            return val.some((obj) => {
                return hasValue(obj, value);
            })
        }
    });
}

it is of course inspired by @Nina Scholz 's solution!

exside
  • 3,736
  • 1
  • 12
  • 19
  • `'array'` is not a possible type returned by `typeof`. – Sebastian Speitel Jun 07 '20 at 22:39
  • Of course it is not =/, you're absolutely right! Fixed and thanks for the notice, for better browser compatibility (if really needed nowadays) one could replase `.isArray()` with `Object.prototype.toString.call(val) === '[object Array]'` – exside Jun 16 '20 at 15:38
0

An answer depends a on how complex you want to get. For example a JSON parsed array doesn't contain functions - and I'm fairly certain it won't contain property value set to a parent node in object tree.

This version returns the property value of the first property name found whilst searching the object tree. undefined is returned if either the named property was not found or has a value of undefined. Some modifications would be needed to tell the difference. It does not re-search parent nodes already being searched, nor try to scan null objects!

let util = {};

util.findVal = (object, propName, searched=[]) => {
  searched.push( object)
  for (let key in object) {
    if (key === propName) {
      return object[key]
    } 
    else {
      let obj = object[ key]
      if( obj && (typeof obj == "object" || typeof obj == "function")) {
          if( searched.indexOf(obj) >=0) {
              continue
          }
          let found = util.findVal(obj, propName, searched)
          if( found != searched) {
              return found
          }
      }
    }
  }
  searched.pop();
  // not in object:
  return searched.length ? searched : undefined
}
traktor
  • 17,588
  • 4
  • 32
  • 53
0

I ended up writing this function. It is a refactor of a function found here: Recursively looping through an object to build a property list

added a depth parameter to avoid stack overflow in chrome devtools.

function iterate(obj, context, search, depth) {
    for (var property in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, property)) {
            if(typeof obj[property] == 'function') continue;

            if( property == search ){
                console.log(context+property);
                return;
            }

            if (typeof obj[property] == "object" && depth < 7) {
                //console.log('--- going in: ' + context+property);
                iterate(obj[property], context+property+'.', search, depth+1);
            }
            /*else {
                console.log(context+property);
            }*/
        }
    }
}
elewinso
  • 2,453
  • 5
  • 22
  • 27
0

Returns the value of the field with the specified name.

data is the root node/object. keyName is a string name of the field/member.

If keyName specifies a field that is itself an object, then that object is returned.

function find (data, keyName) {
  for (const key in data) {

    const entry = data[key]
    if (key === keyName)
      return entry

    if (typeof entry === 'object') {
      const found = find(entry, keyName)

      if (found)
        return found
    }
  }
}

The for loop goes through each field and if that field is an object then it will recurse into that object.

Paul Sumpner
  • 447
  • 7
  • 8
0

Here is a piece of code which find the key you are looking for in your rootObj tree. And add it to the root object. So by the end you will have access to you key like this rootObj[key].

findKeyVal(object, key, rootObj) {
      if(object instanceof Object) {
        let keys = Object.keys(object);
        if(keys.includes(key) && !isNullOrUndefined(object[key])) {
          rootObj[key] = object[key];
          return;
        }
        else {
          keys.filter(k => object[k] instanceof Object).forEach( k => {
            this.findKeyVal(object[k], key, rootObj);
          })
        }
      }
    }
Nadhir Houari
  • 390
  • 4
  • 10
-1

Old question, but to check if the property exists anywhere in the hierarchy of an object, try this simple option

var obj = {
  firstOperand: {
    firstOperand: {
      firstOperand: {
        sweptArea: 5
      }
    }
  }
};

function doesPropertyExists ( inputObj, prop )
{
  return JSON.stringify(obj).indexOf( "\""+ prop +"\":" ) != -1;
};

console.log( doesPropertyExists( obj, "sweptArea" ) );
console.log( doesPropertyExists( obj, "firstOperand" ) );
console.log( doesPropertyExists( obj, "firstOperand22" ) );
gurvinder372
  • 66,980
  • 10
  • 72
  • 94
  • What if the key exists as a value somewhere by accident (or not)? Then this would fail, but nonetheless a nice thinking-outside-the-box idea 8)! – exside Jun 27 '19 at 19:15