Поиск по ключевому слову во вложенном объекте

Скажем, у меня есть объект:

[
    {
        'title': "some title"
        'channel_id':'123we'
        'options': [
                    {
                'channel_id':'abc'
                'image':'http://asdasd.com/all-inclusive-block-img.jpg'
                'title':'All-Inclusive'
                'options':[
                    {
                        'channel_id':'dsa2'
                        'title':'Some Recommends'
                        'options':[
                            {
                                'image':'http://www.asdasd.com'                                 'title':'Sandals'
                                'id':'1'
                                'content':{
                                     ...

Я хочу найти один объект, где id равен 1. Есть ли что-то вроде этого? Я мог бы использовать метод Underscore _.filter, но мне нужно было бы начать сверху и отфильтровать.

Ответы

Ответ 1

Рекурсия - ваш друг. Я обновил функцию для учета массивов свойств:

function getObject(theObject) {
    var result = null;
    if(theObject instanceof Array) {
        for(var i = 0; i < theObject.length; i++) {
            result = getObject(theObject[i]);
            if (result) {
                break;
            }   
        }
    }
    else
    {
        for(var prop in theObject) {
            console.log(prop + ': ' + theObject[prop]);
            if(prop == 'id') {
                if(theObject[prop] == 1) {
                    return theObject;
                }
            }
            if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
                result = getObject(theObject[prop]);
                if (result) {
                    break;
                }
            } 
        }
    }
    return result;
}

обновлен jsFiddle: http://jsfiddle.net/FM3qu/7/

Ответ 2

Если вы хотите получить первый элемент с идентификатором 1 во время поиска объекта, вы можете использовать эту функцию:

function customFilter(object){
    if(object.hasOwnProperty('id') && object["id"] == 1)
        return object;

    for(var i=0; i<Object.keys(object).length; i++){
        if(typeof object[Object.keys(object)[i]] == "object"){
            var o = customFilter(object[Object.keys(object)[i]]);
            if(o != null)
                return o;
        }
    }

    return null;
}

Если вы хотите получить все элементы с идентификатором 1, то (все элементы с идентификатором 1 сохраняются в результате, как вы видите):

function customFilter(object, result){
    if(object.hasOwnProperty('id') && object.id == 1)
        result.push(object);

    for(var i=0; i<Object.keys(object).length; i++){
        if(typeof object[Object.keys(object)[i]] == "object"){
            customFilter(object[Object.keys(object)[i]], result);
        }
    }
}

Ответ 3

Что сработало для меня, так это ленивый подход, а не алгоритмически ленивый

if( JSON.stringify(object_name).indexOf("key_name") > -1 ) {
    console.log("Key Found");
}
else{
    console.log("Key not Found");
}

Ответ 4

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

function findObjects(obj, targetProp, targetValue, finalResults) {

  function getObject(theObject) {
    let result = null;
    if (theObject instanceof Array) {
      for (let i = 0; i < theObject.length; i++) {
        getObject(theObject[i]);
      }
    }
    else {
      for (let prop in theObject) {
        if(theObject.hasOwnProperty(prop)){
          console.log(prop + ': ' + theObject[prop]);
          if (prop === targetProp) {
            console.log('--found id');
            if (theObject[prop] === targetValue) {
              console.log('----found porop', prop, ', ', theObject[prop]);
              finalResults.push(theObject);
            }
          }
          if (theObject[prop] instanceof Object || theObject[prop] instanceof Array){
            getObject(theObject[prop]);
          }
        }
      }
    }
  }

  getObject(obj);

}

Он находит любой объект внутри объекта obj с именем и значением свойства, совпадающим с targetProp и targetValue и отправляет его в массив finalResults. И вот jsfiddle, чтобы поиграться: https://jsfiddle.net/alexQch/5u6q2ybc/

Ответ 5

Я создал библиотеку для этой цели: https://github.com/dominik791/obj-traverse

Вы можете использовать метод findFirst() следующим образом:

var foundObject = findFirst(rootObject, 'options', { 'id': '1' });

И теперь переменная foundObject хранит ссылку на объект, который вы ищете.

Ответ 6

Улучшен ответ @haitaka с использованием ключа и предиката

function  deepSearch (object, key, predicate) {
    if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) return object

    for (let i = 0; i < Object.keys(object).length; i++) {
      if (typeof object[Object.keys(object)[i]] === "object") {
        let o = deepSearch(object[Object.keys(object)[i]], key, predicate)
        if (o != null) return o
      }
    }
    return null
}

Так что это может быть вызвано как:

var result = deepSearch(myObject, 'id', (k, v) => v === 1);

или же

var result = deepSearch(myObject, 'title', (k, v) => v === 'Some Recommends');

Вот jsFiddle: http://jsfiddle.net/ktdx9es7

Ответ 7

Другой (несколько глупый) вариант - использовать естественную рекурсивную природу JSON.stringify и передать ей функцию-заменителя, которая запускается на каждом вложенном объекте в процессе строкового преобразования:

const input = [{
  'title': "some title",
  'channel_id': '123we',
  'options': [{
    'channel_id': 'abc',
    'image': 'http://asdasd.com/all-inclusive-block-img.jpg',
    'title': 'All-Inclusive',
    'options': [{
      'channel_id': 'dsa2',
      'title': 'Some Recommends',
      'options': [{
        'image': 'http://www.asdasd.com',
        'title': 'Sandals',
        'id': '1',
        'content': {}
      }]
    }]
  }]
}];

console.log(findNestedObj(input, 'id', '1'));

function findNestedObj(entireObj, keyToFind, valToFind) {
  let foundObj;
  JSON.stringify(input, (_, nestedValue) => {
    if (nestedValue && nestedValue[keyToFind] === valToFind) {
      foundObj = nestedValue;
    }
    return nestedValue;
  });
  return foundObj;
};

Ответ 8

Улучшенный ответ для учета циклических ссылок внутри объектов. Он также отображает путь, который понадобился, чтобы туда добраться.

В этом примере я ищу iframe, который, как я знаю, находится где-то внутри глобального объекта:

const objDone = []
var i = 2
function getObject(theObject, k) {
    if (i < 1 || objDone.indexOf(theObject) > -1) return
    objDone.push(theObject)
    var result = null;
    if(theObject instanceof Array) {
        for(var i = 0; i < theObject.length; i++) {
            result = getObject(theObject[i], i);
            if (result) {
                break;
            }   
        }
    }
    else
    {
        for(var prop in theObject) {
            if(prop == 'iframe' && theObject[prop]) {
                i--;
                console.log('iframe', theObject[prop])
                return theObject[prop]
            }
            if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
                result = getObject(theObject[prop], prop);
                if (result) {
                    break;
                }
            } 
        }
    }
    if (result) console.info(k)
    return result;
}

Выполнение следующего: getObject(reader, 'reader') дал следующий вывод и элемент iframe в конце:

iframe // (The Dom Element)
_views
views
manager
rendition
book
reader

ПРИМЕЧАНИЕ. Путь в обратном порядке: reader.book.rendition.manager.views._views.iframe

Ответ 9

Я хотел бы предложить поправку к ответу Zach/RegularMike (но у меня нет "репутации", чтобы иметь возможность комментировать!). Я нашел там решение очень полезной основой, но пострадало в моем приложении, потому что, если бы в массивах были строки, он рекурсивно вызывал бы функцию для каждого символа в строке (что приводило к сбою браузеров IE11 и Edge с "нехваткой стека"). ошибки). Моя простая оптимизация состояла в том, чтобы добавить тот же тест, который использовался в рекурсивном вызове предложения "object", к тому, что был в предложении "array":

if (arrayElem instanceof Object || arrayElem instanceof Array) {

Таким образом, мой полный код (который теперь ищет все экземпляры определенного ключа, так немного отличается от исходного требования):

// Get all instances of specified property deep within supplied object
function getPropsInObject(theObject, targetProp) {
    var result = [];
    if (theObject instanceof Array) {
        for (var i = 0; i < theObject.length; i++) {
            var arrayElem = theObject[i];
            if (arrayElem instanceof Object || arrayElem instanceof Array) {
                result = result.concat(getPropsInObject(arrayElem, targetProp));
            }
        }
    } else {
        for (var prop in theObject) {
            var objProp = theObject[prop];
            if (prop == targetProp) {
                return theObject[prop];
            }
            if (objProp instanceof Object || objProp instanceof Array) {
                result = result.concat(getPropsInObject(objProp, targetProp));
            }
        }
    }
    return result;
}

Ответ 10

Я бы постарался не изобретать велосипед. Мы используем object-scan для всех наших потребностей в обработке данных. Это концептуально очень просто, но допускает много интересных вещей. Вот как бы вы решили свой конкретный вопрос

Определение данных

const data = [{
  'title': "some title",
  'channel_id': '123we',
  'options': [{
    'channel_id': 'abc',
    'image': 'http://asdasd.com/all-inclusive-block-img.jpg',
    'title': 'All-Inclusive',
    'options': [{
      'channel_id': 'dsa2',
      'title': 'Some Recommends',
      'options': [{
        'image': 'http://www.asdasd.com',
        'title': 'Sandals',
        'id': '1',
        'content': {}
      }]
    }]
  }]
}];

Logic

const objectScan = require('object-scan');

const scanner = (input) => {
  let obj = null;
  objectScan(['**.id'], {
    filterFn: (key, value, { parents }) => {
      if (value === '1') {
        obj = parents[0];
      }
    },
    breakFn: () => obj !== null
  })(data);
  return obj;
};

const result = scanner(data);

Выход

// result =>
{
  "image": "http://www.asdasd.com",
  "title": "Sandals",
  "id": "1",
  "content": {}
}

Ответ 11

Если вы уже используете Underscore, используйте _.find()

_.find(yourList, function (item) {
    return item.id === 1;
});