Поиск рекурсивно для значения в объекте по имени свойства

Я создаю функцию утилиты, которая должна искать имя свойства и возвращать его значение после его обнаружения. Он должен делать это рекурсивно:

// 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')

Однако консольный журнал работает навсегда, и браузер выходит из строя. Что я делаю неправильно?

EDIT:

Вот как я вызываю функцию:

// Input

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

this.findProgress(item)

methods: {
  findProgress (item) {
    return util.findVal(item, this.propName)
  }
}

Ответ 1

Вы можете использовать Object.keys и итерации с помощью 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'));

Ответ 2

В вашем коде есть несколько ошибок:

  • Вы рекурсивно звоните util.findVal, но не возвращаете результат вызова. Код должен быть return util.findVal(...)
  • Вы не передаете имя атрибута key рекурсивному вызову
  • Вы не обрабатываете возможность цикла ссылок
  • Если объект содержит ключ, а также подобъект, содержащий ключ, возвращаемое значение является случайным (зависит от последовательности, в которой анализируются ключи)

Третья проблема заключается в том, что может вызвать бесконечную рекурсию, например:

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

если вы просто продолжаете искать рекурсивный поиск в obj1 или obj2, это может привести к бесконечной рекурсии.

К сожалению, по причинам, неясным для меня в Javascript, невозможно узнать объект "идентичность"... (что делает Python id(x)), вы можете только сравнить объект с другим. Это означает, что для того, чтобы узнать, был ли объект уже виден в прошлом, вам необходимо выполнить линейное сканирование с известными объектами.

В ES6 добавлена возможность проверки идентичности объекта с помощью Set и Map, где объекты можно использовать в качестве ключей. Это позволяет быстрее (сублинейнее) время поиска.

Например, поисковое решение, которое выполняется в порядке глубины:

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;
}

учитывая объект и имя атрибута, возвращает все значения, найденные с этим именем, на первой глубине их обнаружения (может быть несколько значений: например, при поиске {x:{z:1}, y:{z:2}} ключа "z" двух значений находятся на той же глубине).

Функция также корректно обрабатывает структуры, ссылающиеся на себя, избегая бесконечного поиска.

Ответ 3

попробуйте заменить команду else следующим образом

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

Ответ 4

Подумайте об этом, если ключ не найден.

Я думаю, вы могли бы сделать что-то вроде этого вместо поиска

return object[propName] || null 

В вашем коде отсутствовала точка останова, я предполагаю, что вы пытаетесь выполнить поиск внутри всего объекта, а не только непосредственно связанных атрибутов, поэтому здесь вы можете редактировать код

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
     }
  }
 }
}

Ответ 5

Я думаю, вы говорите, что хотите найти имя свойства в любом месте рекурсивно в дереве объектов свойств и под-свойств. Если да, вот как я подхожу к этому:

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);

Ответ 6

Нашел этот вопрос в области необходимости общего решения, чтобы проверить, содержит ли объект конкретное значение где-либо в его иерархии (независимо от ключа), который, конечно, может включать массивы. Таким образом, следующее не отвечает на вопросы OPs напрямую или не улучшает другие решения, но может помочь другим найти то же, что я сделал, и найти этот пост:

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 && typeof val === 'array') {
            return val.some((obj) => {
                return hasValue(obj, value);
            })
        }
    });
}

это конечно вдохновлено решением @Nina Scholz!

Ответ 7

Ответ зависит от того, насколько сложным вы хотите получить. Например, анализируемый массив JSON не содержит функций - и я уверен, что он не будет содержать значение свойства, установленное родительским node в дереве объектов.

Эта версия возвращает значение свойства первого имени свойства, найденного во время поиска в дереве объектов. undefined возвращается, если либо именованное свойство не было найдено, либо имеет значение undefined. Для изменения разницы потребуются некоторые изменения. Он не повторно ищет родительские узлы, которые уже ищут, и не пытается сканировать нулевые объекты!

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
}

Ответ 8

Я знаю, что это старый пост, но я нашел полезным ответить на проблему, с которой я столкнулся, с рекурсивным поиском значения по этому ключу. Я еще раз разработал ответ, данный Ниной Шольц, и придумал следующее. Он должен быть быстрее, поскольку он не создает массив всех ключей каждый раз, когда он рекурсивно вызывается. Кроме того, это будет явно возвращать false, если ключ не найден.

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'));

Ответ 9

Я закончил писать эту функцию. Это рефакторинг функции, найденной здесь: рекурсивный цикл по объекту для построения списка свойств

добавлен параметр глубины, чтобы избежать в 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);
            }*/
        }
    }
}

Ответ 10

Возвращает значение поля с указанным именем.

data является корневым узлом/объектом. keyName - это строковое имя поля/члена.

Если keyName указывает поле, которое само является объектом, этот объект возвращается.

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
    }
  }
}

Цикл for проходит через каждое поле, и если это поле является объектом, оно будет возвращаться в этот объект.

Ответ 11

Не пишите свою собственную утилиту, если можете ее избежать.

Используйте что-то вроде jsonpath

Некоторые примеры поддерживаемого синтаксиса:

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    

Ответ 12

Старый вопрос, но чтобы проверить, существует ли свойство в любом месте в иерархии объекта, попробуйте эту простую опцию

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" ) );