124

Let's say I have an object:

[
    {
        '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':{
                                     ...

I want to find the one object where the id is 1. Is there a function for something like this? I could use Underscore's _.filter method, but I would have to start at the top and filter down.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Harry
  • 13,091
  • 29
  • 107
  • 167

21 Answers21

107

Recursion is your friend. I updated the function to account for property arrays:

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

updated jsFiddle: http://jsfiddle.net/FM3qu/7/

regularmike
  • 1,167
  • 10
  • 22
Zach
  • 3,157
  • 1
  • 19
  • 32
  • 1
    But wait, look at my code, options could have many objects [{},{}]. how would this work? – Harry Mar 20 '13 at 13:18
  • There. I mentioned before that it needed to be updated to account for arrays so there you go. – Zach Mar 20 '13 at 13:32
  • 1
    This does not work if you have more than one objects in options – Harry Mar 21 '13 at 09:29
  • 1
    Fixed. I added the result variable and the else logic. The updated jsFiddle shows it working. – Zach Mar 21 '13 at 13:48
  • 1
    Zack I modified your code a little, it works nicely now. Thanks so much! It seems I cannot access jsfiddle at the moment. Ill share my changes once it works – Harry Mar 22 '13 at 09:13
  • It seems that once it gets the obeject at "return theObject", it does not break out, but continues and sets the result to null again. – Harry Mar 22 '13 at 10:01
  • If that is the case, then just add a check to see if it's null after the return and break out of the function then. – Zach Mar 25 '13 at 13:40
  • @Harry that's true. Just edited to check for non-null result and break out of for loop. – regularmike Jun 18 '15 at 21:40
  • Because that was Harry's requirement. Adjust to your need. You could easily pass in two parameters, one for the property name and one for the value. – Zach May 17 '16 at 11:59
  • @Zach I saw your solution for this question and was wondering if you could take a look at the json I tried the solution you used here but was unsuccessful. http://stackoverflow.com/questions/38208550/finding-an-object-deep-in-a-nested-json-object – Jacob Brauchler Jul 05 '16 at 21:53
  • yous can use js Array `some`, more simple instead of a for loop – Nassim Mesdour Mar 09 '21 at 11:51
56

Another (somewhat silly) option is to exploit the naturally recursive nature of JSON.stringify, and pass it a replacer function which runs on each nested object during the stringification process:

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(entireObj, (_, nestedValue) => {
    if (nestedValue && nestedValue[keyToFind] === valToFind) {
      foundObj = nestedValue;
    }
    return nestedValue;
  });
  return foundObj;
};
KnowYourElements
  • 392
  • 1
  • 12
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
45

What worked for me was this lazy approach, not algorithmically lazy ;)

if( JSON.stringify(object_name).indexOf("key_name") > -1 ) {
    console.log("Key Found");
}
else{
    console.log("Key not Found");
}
abhinav1602
  • 1,190
  • 17
  • 18
  • 1
    what do you think about using this instead:```if( JSON.stringify(object_name).indexOf('{"key_name":') > -1 ) {```including the left curly-bracket, double-quotes and the colon that JSON.stringify adds? i am thinking it might be a nice double-check to make sure the key_name is actually a key and not somehow in the "value" part. – edwardsmarkf Mar 22 '19 at 19:01
  • 5
    Potentially good however fails tests that have that key_name somewhere in a value. Also doesn't return the object containing the id – Ranga May 19 '19 at 03:01
  • @Ranga... agreed, this solution works only when you just have to check if the key exists in an object provided that it doesn't have the key_name present in one of the values.... Although, we can modify the code to include '"' around the key name inside indexOf function if we have to make sure it is present only in keys.(again key_name with '"' around it shouldn't be present in the values. – abhinav1602 May 19 '19 at 12:20
  • 4
    I love it! `JSON.stringify(object).includes(text)` – Marcelo Pereira Rodrigues Dec 05 '19 at 04:18
  • This finds out *if* the key is present, but the OP asks how to get the object whose id property has a value of 1. This would make an answer on https://stackoverflow.com/questions/2631001/test-for-existence-of-nested-javascript-object-key (except for the fact that it matches whether the string exists anywhere in the JSON, not that it has the key. – Heretic Monkey Aug 18 '21 at 14:41
23

If you want to get the first element whose id is 1 while object is being searched, you can use this function:

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

If you want to get all elements whose id is 1, then (all elements whose id is 1 are stored in result as you see):

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);
        }
    }
}
zalog
  • 683
  • 9
  • 21
haitaka
  • 1,832
  • 12
  • 21
  • 1
    Your solution is better then one that @regularmike suggested, since I could customize it by adding key and value searching. It works on yours, and it doesn't on his. http://jsfiddle.net/76fp54xt/ Working example – Iulian Pinzaru Jan 31 '19 at 23:17
16

Improved @haitaka answer, using the key and predicate

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++) {
      let value = object[Object.keys(object)[i]];
      if (typeof value === "object" && value != null) {
        let o = deepSearch(object[Object.keys(object)[i]], key, predicate)
        if (o != null) return o
      }
    }
    return null
}

So this can be invoked as:

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

or

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

Here is the demo: http://jsfiddle.net/a21dx6c0/

EDITED

In the same way you can find more than one object

function deepSearchItems(object, key, predicate) {
        let ret = [];
        if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) {
            ret = [...ret, object];
        }
        if (Object.keys(object).length) {
            for (let i = 0; i < Object.keys(object).length; i++) {
                let value = object[Object.keys(object)[i]];
                if (typeof value === "object" && value != null) {
                    let o = this.deepSearchItems(object[Object.keys(object)[i]], key, predicate);
                    if (o != null && o instanceof Array) {
                        ret = [...ret, ...o];
                    }
                }
            }
        }
        return ret;
    }
Iulian Pinzaru
  • 400
  • 3
  • 10
  • 2
    Note: `typeof null === 'object'`, so if any values in the object are null, they will cause the function to crash when it tries to recurse. So you should also check for truthiness before recursing. – Rafi Jul 21 '19 at 12:50
  • @Rafi thank you for mentioning it, edited the answer. – Iulian Pinzaru Aug 27 '20 at 22:48
  • key and predicate was helpful for my usage. worked like a charm – Saran Raj Mar 18 '23 at 09:51
11

If you're into the whole ES6 thing you can use

const findByKey = (obj, kee) => {
    if (kee in obj) return obj[kee];
    for(n of Object.values(obj).filter(Boolean).filter(v => typeof v === 'object')) {
        let found = findByKey(n, kee)
        if (found) return found
    }
}

const findByProperty = (obj, predicate) => {
    if (predicate(obj)) return obj
    for(n of Object.values(obj).filter(Boolean).filter(v => typeof v === 'object')) {
        let found = findByProperty(n, predicate)
        if (found) return found
    }
}

find by value is going to be a little different

let findByValue = (o, val) => {
    if (o === val) return o;
    if (o === NaN || o === Infinity || !o || typeof o !== 'object') return;
    if (Object.values(o).includes(val)) return o;
    for (n of Object.values(o)) {
        const found = findByValue(n, val)
        if (found) return n
    }
}

then they can be used like this

const arry = [{ foo: 0 }, null, { bar: [{ baz: { nutherKey: undefined, needle: "gotcha!" } }]}]
const obj = { alice: Infinity, bob: NaN, charlie: "string", david: true, ebert: arry }

findByKey(obj, 'needle')
// 'gotcha!'

findByProperty(obj, val => val.needle === 'gotcha!')
// { nutherKey: undefined, needle: "gotcha!" }

findByValue(obj, 'gotcha!')
// { nutherKey: undefined, needle: "gotcha!" }
olle
  • 451
  • 2
  • 7
8

I found this page through googling for the similar functionalities. Based on the work provided by Zach and regularmike, I created another version which suits my needs.
BTW, teriffic work Zah and regularmike! I'll post the code here:

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

}

What it does is it find any object inside of obj with property name and value matching to targetProp and targetValue and will push it to the finalResults array. And Here's the jsfiddle to play around: https://jsfiddle.net/alexQch/5u6q2ybc/

Alex Quan
  • 81
  • 1
  • 3
  • 3
    this could be further improved by `return`ing the `finalResults` array instead of accepting another variable as input to write to – RozzA Oct 07 '17 at 21:54
7

I've created library for this purpose: https://github.com/dominik791/obj-traverse

You can use findFirst() method like this:

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

And now foundObject variable stores a reference to the object that you're looking for.

dominik791
  • 692
  • 6
  • 17
6

Another recursive solution, that works for arrays/lists and objects, or a mixture of both:

function deepSearchByKey(object, originalKey, matches = []) {

    if(object != null) {
        if(Array.isArray(object)) {
            for(let arrayItem of object) {
                deepSearchByKey(arrayItem, originalKey, matches);
            }
        } else if(typeof object == 'object') {

            for(let key of Object.keys(object)) {
                if(key == originalKey) {
                    matches.push(object);
                } else {
                    deepSearchByKey(object[key], originalKey, matches);
                }

            }

        }
    }


    return matches;
}

usage:

let result = deepSearchByKey(arrayOrObject, 'key'); // returns an array with the objects containing the key
Ali Alnoaimi
  • 2,278
  • 2
  • 21
  • 31
5

You can use javascript some function inside a recursive function. The advantage of some is to stop looping once the child is founded. Do not use map that would be slow in large data.

const findChild = (array, id) => {
  let result;
  array.some(
    (child) =>
      (child.id === id && (result = child)) ||
      (result = findChild(child.options || [], id))
  );
  return result;
};

findChild(array, 1)
Nassim Mesdour
  • 166
  • 1
  • 8
5

Found the answer I was looking for, especially Ali Alnoaimi's solution. I made some small adjustments to allow for the search of the value as well

function deepSearchByKey(object, originalKey, originalValue, matches = []) {
if (object != null) {
  if (Array.isArray(object)) {
    for (let arrayItem of object) {
      deepSearchByKey(arrayItem, originalKey, originalValue, matches);
    }
  } else if (typeof object == 'object') {
    for (let key of Object.keys(object)) {
      if (key == originalKey) {
        if (object[key] == originalValue) {
          matches.push(object);
        }
      } else {
        deepSearchByKey(object[key], originalKey, originalValue, matches);
      }
    }
  }
}

return matches;
}

To use:

let result = deepSearchByKey(arrayOrObject, 'key', 'value');

This will return the object containing the matching key and value.

FuZu
  • 51
  • 1
  • 3
3

We use object-scan for our data processing. It's conceptually very simple, but allows for a lot of cool stuff. Here is how you would solve your specific question

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

const find = (id, input) => objectScan(['**'], {
  abort: true,
  rtn: 'value',
  filterFn: ({ value }) => value.id === id
})(input);

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: {} }] }] }] }];

console.log(find('1', data));
// => { image: 'http://www.asdasd.com', title: 'Sandals', id: '1', content: {} }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>

Disclaimer: I'm the author of object-scan

vincent
  • 1,953
  • 3
  • 18
  • 24
3

Just use recursive function.
See example below:

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: {},
              }
            ]
          }
        ]
      }
    ]
  }
]

function _find(collection, key, value) {
  for (const o of collection) {
    for (const [k, v] of Object.entries(o)) {
      if (k === key && v === value) {
        return o
      }
      if (Array.isArray(v)) {
        const _o = _find(v, key, value)
        if (_o) {
          return _o
        }
      }
    }
  }
}

console.log(_find(data, 'channel_id', 'dsa2'))
Thiago Lagden
  • 161
  • 1
  • 4
2

@Iulian Pinzaru's answer was almost exactly what I needed, but it doesn't work if your objects have any null values. This version fixes that.

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++) {
    const nextObject = object[Object.keys(object)[i]];
    if (nextObject && typeof nextObject === "object") {
      let o = deepSearch(nextObject, key, predicate)
      if (o != null) return o
    }
  }
  return null
}
Villarrealized
  • 867
  • 1
  • 6
  • 13
2

          function getPropFromObj(obj, prop) {
            let valueToFindByKey;
            if (!Array.isArray(obj) && obj !== null && typeof obj === "object") {
              if (obj.hasOwnProperty(prop)) {
                
                 valueToFindByKey = obj[prop];
               console.log(valueToFindByKey);
              } else {
               
                let i;
                for (i = 0; i < Object.keys(obj).length; i++) {
              
                
                    getPropFromObj(obj[Object.keys(obj)[i]], prop);
                }
              }
              
            }
            return null;
           
          }
     

        const objToInvestigate = {
            employeeInformation: {
              employees: {
                name: "surya",
                age: 27,
                job: "Frontend Developer",
              },
            },
          };
          getPropFromObj(objToInvestigate, "name");
  1. Detecting the key in the deeply nested object.
  2. Finally return the value of the detected key.
Surya
  • 31
  • 5
1

Improved answer to take into account circular references within objects. It also displays the path it took to get there.

In this example, I am searching for an iframe that I know is somewhere within a global object:

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

Running the following: getObject(reader, 'reader') gave the following output and the iframe element in the end:

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

NOTE: The path is in reverse order reader.book.rendition.manager.views._views.iframe

Gerardlamo
  • 1,505
  • 15
  • 21
1

I'd like to suggest an amendment to Zach/RegularMike's answer (but don't have the "reputation" to be able to comment!). I found there solution a very useful basis, but suffered in my application because if there are strings within arrays it would recursively call the function for every character in the string (which caused IE11 & Edge browsers to fail with "out of stack space" errors). My simple optimization was to add the same test used in the "object" clause recursive call to the one in the "array" clause:

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

Thus my full code (which is now looking for all instances of a particular key, so slightly different to the original requirement) is:

// 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;
}
Richard T
  • 11
  • 1
1

Some time ago I have made a small lib find-and, which is available on npm, for working with nested objects in a lodash manner. There's the returnFound function which returns the found object, or an object array if there's more than one object found.

E.g.,

const findAnd = require('find-and');

const a = [
  {
    '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':{},
              },
            ],
          },
        ],
      },
    ],
  },
];

findAnd.returnFound(a, {id: '1'});

returns

{
  'image':'http://www.asdasd.com',
  'title':'Sandals',
  'id':'1',
  'content':{},
}
Arfeo
  • 870
  • 7
  • 20
0

This function (main()) allows you to get all objects within a JSON whose key is user-defined. Here is an example:

function main(obj = {}, property) {
  const views = [];
  
  function traverse(o) {
    for (var i in o) {
      if (i === property) views.push(o[i]);
      if (!!o[i] && typeof(o[i]) == "object") traverse(o[i]);
    }
  }

  traverse(obj);
  return views;
}

const obj = {
  id: 'id at level 1',
  level2: {
    id: 'id at level 2',
    level3: {
      id: 'id at level 3',
      level4: {
        level5: {
          id: 'id at level 5'
        }
      }
    }
  },
  text: ''
}

console.log(main(obj, 'id'));
Michael M.
  • 10,486
  • 9
  • 18
  • 34
LuisP
  • 11
  • 2
  • 1
    Even though this answer is a little irrelevant to the question, this is just what I need. Thanks! –  Feb 21 '23 at 12:45
0

Much easy and cleaner way

const findElement = (searchObj, searchKey) => Object.keys(searchObj).forEach(key => {
  if (key === searchKey) {
    preloadingImgObj = searchObj[key];
    return searchObj[key];
  }
  if (typeof searchObj[key] === 'object' && searchObj[key] !== undefined && searchObj[key] !== null) {
    return findElement(searchObj[key], searchKey);
  }
});

Thank me later

Shaikh Arbaaz
  • 155
  • 1
  • 4
  • What is `preloadingImgObj`? Also, your code looks for a value set at a specific property, not an object with a specific property value (i.e. the value assigned to `id` instead of the object with a given id). Please make sure to highlight what your answer adds to the numerous existing answers. How is it "easy and cleaner"? – Moritz Ringler May 11 '23 at 12:50
-18

If you're already using Underscore, use _.find()

_.find(yourList, function (item) {
    return item.id === 1;
});
rhodesjason
  • 4,904
  • 9
  • 43
  • 59
  • It would work If there item was in the first level of the object but its not, its nested with in – Harry Mar 20 '13 at 12:48
  • 2
    Dang. I was looking on my phone and the way the object wrapped made it look like it was on the first level. I thought it seemed too easy. – rhodesjason Mar 20 '13 at 13:19