Chris Frank
Chris Frank

Reputation: 4452

Searching deeply nested object array for a single key

I am trying to search through a deeply nested array and find if a key exists anywhere inside. I have written up a piece of code which does the traversal, but because it is not recursive (only self calling) it cannot return whether or not it has found anything. It just returns undefined since it reaches the end of the function on one of passes.

I was wondering if there was a way I could do this which would allow me to return true on the first occurrence of a specific key.

Here is a JS bin of what I have been working with so far:

https://jsbin.com/qaxuwajuso/edit?js,console

And here is a direct paste of the code from the above example:

function traverse(item, key) {
    if (typeof item === 'object' && !Array.isArray(item) && item !== null) {
        // Object
        for (let itemKey in item) {
            if (itemKey === key) {
                // Is it possible to return true and break out of the function here?
                console.log('found the key: ' + itemKey + ' With value: ' + item[itemKey]);
            }

            traverse(item[itemKey], key);
        }
    } else if (Array.isArray(item)) {
        // Array
        for (let i = 0; i < item.length; ++i) {
            traverse(item[i], key);
        }
    }
}

Any help would be greatly appreciated. Thank you for your time!

Upvotes: 1

Views: 541

Answers (3)

Илья Зелень
Илья Зелень

Reputation: 8088

My variant:

const data = [{id: '2144d998-4c33-4b03-93d2-f6c675b24508', element: 'div', props: {className: 'testing', name: [{first: 'John', last: {statePath3: 'lastName', anArray: [{anObject: {anotherArray: [{doesItWork: {statePath1: 'hello',},},],},},],},}, {first: 'Jane', last: {statePath: 'lastName',},},],}, children: 'hi',},];

function traverse(data, find) {
  for (let k in data) {
    let deepHaveKey = typeof data[k] === 'object' && traverse(data[k], find)
    
    if (find === k || deepHaveKey)
      return true
  }
  
  return false
}

console.log(traverse(data, 'statePath')); // true
console.log(traverse(data, 'state')); // false

Upvotes: 0

Nenad Vracar
Nenad Vracar

Reputation: 122047

You could use for...in and store result in one var and then check that var before you call function again and break loop if value is found.

const data = [{"id":"2144d998-4c33-4b03-93d2-f6c675b24508","statePath":"div","props":{"className":"testing","name":[{"first":"John","last":{"statePath":"lastName","anArray":[{"anObject":{"anotherArray":[{"doesItWork":{"statePath":"hello"}}]}}]}},{"first":"Jane","last":{"statePath":"lastName"}}]},"children":"hi"}]

function traverse(item, key) {
  let result = false;

  for (var i in item) {
    if (i == key) {
      result = true;
      break;
    }
    if (typeof item[i] == 'object' && !result) {
      result = traverse(item[i], key)
    }
  }

  return result
}

console.log(traverse(data, 'statePath'))

Upvotes: 1

James
James

Reputation: 22247

Sure you just need to return a flag of some kind to trigger the loops to stop

/*
 * I am trying to search the following json array for any occurance of the key "statePath".
 * In a perfect world I would be able to find the first occurance, and return true from the
 * function.
 *
 * The following data is not real, I was just trying to write as much nested stuff as possible
 * to test that it traverses as far as needed.
 */

const data = [
    {
        id: '2144d998-4c33-4b03-93d2-f6c675b24508',
        element: 'div',
        props: {
            className: 'testing',
            name: [
                {
                    first: 'John',
                    last: {
                        statePath: 'lastName',
                        anArray: [
                          {
                            anObject: {
                              anotherArray: [
                                {
                                  doesItWork: {
                                    statePath: 'hello',
                                  },
                                },
                              ],
                            },
                          },
                        ],
                    },
                },
                {
                    first: 'Jane',
                    last: {
                        statePath: 'lastName',
                    },
                },
            ],
        },
        children: 'hi',
    },
];

function traverse(item, key) {
  if (typeof item === 'object' && !Array.isArray(item) && item !== null) {
    // Object
    for (let itemKey in item) {
      if (itemKey === key) {
        console.log('found the key: ' + itemKey + ' With value: ' + item[itemKey]);
        
        // not sure what you want the end "return" of the func to be, I'm returning the value.  You could return true here instead, you could return a reference to the parent object, lots of possibilities
        return item[itemKey];
      }
      var found = traverse(item[itemKey], key);
      if (found !== undefined) return found;
      // otherwise keep looking
    }
  } else if (Array.isArray(item)) {
    // Array
    for (let i = 0; i < item.length; ++i) {
      var found = traverse(item[i], key);
      if (found !== undefined) return found;
    }
  }
}

var value = traverse(data, 'statePath');
console.log("value is " + value);

Upvotes: 2

Related Questions