Find all values by specific key in a deep nested object

24,385

Solution 1

You could make a recursive function like this:

idArray = []

function func(obj) {
  idArray.push(obj.id)
  if (!obj.children) {
    return
  }

  obj.children.forEach(child => func(child))
}

Snippet for your sample:

const myObj = {
  id: 1,
  children: [{
      id: 2,
      children: [{
        id: 3
      }]
    },
    {
      id: 4,
      children: [{
        id: 5,
        children: [{
          id: 6,
          children: [{
            id: 7,
          }]
        }]
      }]
    },
  ]
}

idArray = []

function func(obj) {
  idArray.push(obj.id)
  if (!obj.children) {
    return
  }

  obj.children.forEach(child => func(child))
}

func(myObj)
console.log(idArray)

Solution 2

This is a bit late but for anyone else finding this, here is a clean, generic recursive function:

function findAllByKey(obj, keyToFind) {
  return Object.entries(obj)
    .reduce((acc, [key, value]) => (key === keyToFind)
      ? acc.concat(value)
      : (typeof value === 'object')
      ? acc.concat(findAllByKey(value, keyToFind))
      : acc
    , [])
}

// USAGE
findAllByKey(myObj, 'id')

Solution 3

I found steve's answer to be most suited for my needs in extrapolating this out and creating a general recursive function. That said, I encountered issues when dealing with nulls and undefined values, so I extended the condition to accommodate for this. This approach uses:

Array.reduce() - It uses an accumulator function which appends the value's onto the result array. It also splits each object into it's key:value pair which allows you to take the following steps:

  1. Have you've found the key? If so, add it to the array;
  2. If not, have I found an object with values? If so, the key is possibly within there. Keep digging by calling the function on this object and append the result onto the result array; and
  3. Finally, if this is not an object, return the result array unchanged.

Hope it helps!

const myObj = {
  id: 1,
  children: [{
      id: 2,
      children: [{
        id: 3
      }]
    },
    {
      id: 4,
      children: [{
        id: 5,
        children: [{
          id: 6,
          children: [{
            id: 7,
          }]
        }]
      }]
    },
  ]
}

function findAllByKey(obj, keyToFind) {
  return Object.entries(obj)
    .reduce((acc, [key, value]) => (key === keyToFind)
      ? acc.concat(value)
      : (typeof value === 'object' && value)
      ? acc.concat(findAllByKey(value, keyToFind))
      : acc
    , []) || [];
}

const ids = findAllByKey(myObj, 'id');

console.log(ids)

Solution 4

You can make a generic recursive function that works with any property and any object.

This uses Object.entries(), Object.keys(), Array.reduce(), Array.isArray(), Array.map() and Array.flat().

The stopping condition is when the object passed in is empty:

const myObj = {
  id: 1,
  anyProp: [{
    id: 2,
    thing: { a: 1, id: 10 },
    children: [{ id: 3 }]
  }, {
    id: 4,
    children: [{
      id: 5,
      children: [{
        id: 6,
        children: [{ id: 7 }]
      }]
    }]
  }]
};

const getValues = prop => obj => {
  if (!Object.keys(obj).length) { return []; }

  return Object.entries(obj).reduce((acc, [key, val]) => {
    if (key === prop) {
      acc.push(val);
    } else {
      acc.push(Array.isArray(val) ? val.map(getIds).flat() : getIds(val));
    }
    return acc.flat();
  }, []);
}

const getIds = getValues('id');

console.log(getIds(myObj));

Solution 5

Note: children is a consistent name, and id's wont exist outside of a children object.

So from the obj, I would like to produce an array like this:

const idArray = [1, 2, 3, 4, 5, 6, 7]

Given that the question does not contain any restrictions on how the output is derived from the input and that the input is consistent, where the value of property "id" is a digit and id property is defined only within "children" property, save for case of the first "id" in the object, the input JavaScript plain object can be converted to a JSON string using JSON.stringify(), RegExp /"id":\d+/g matches the "id" property and one or more digit characters following the property name, which is then mapped to .match() the digit portion of the previous match using Regexp \d+ and convert the array value to a JavaScript number using addition operator +

const myObject = {"id":1,"children":[{"id":2,"children":[{"id":3}]},{"id":4,"children":[{"id":5,"children":[{"id":6,"children":[{"id":7}]}]}]}]};

let res = JSON.stringify(myObject).match(/"id":\d+/g).map(m => +m.match(/\d+/));

console.log(res);

JSON.stringify() replacer function can alternatively be used to .push() the value of every "id" property name within the object to an array

const myObject = {"id":1,"children":[{"id":2,"children":[{"id":3}]},{"id":4,"children":[{"id":5,"children":[{"id":6,"children":[{"id":7}]}]}]}]};

const getPropValues = (o, prop) => 
  (res => (JSON.stringify(o, (key, value) => 
    (key === prop && res.push(value), value)), res))([]);

let res = getPropValues(myObject, "id");

console.log(res);

Since the property values of the input to be matched are digits, all the JavaScript object can be converted to a string and RegExp \D can be used to replace all characters that are not digits, spread resulting string to array, and .map() digits to JavaScript numbers

let res = [...JSON.stringify(myObj).replace(/\D/g,"")].map(Number)
Share:
24,385
cup_of
Author by

cup_of

Updated on December 04, 2021

Comments

  • cup_of
    cup_of over 2 years

    How would I find all values by specific key in a deep nested object?

    For example, if I have an object like this:

    const myObj = {
      id: 1,
      children: [
        {
          id: 2,
          children: [
            {
              id: 3
            }
          ]
        },
        {
          id: 4,
          children: [
            {
              id: 5,
              children: [
                {
                  id: 6,
                  children: [
                    {
                      id: 7,
                    }
                  ]
                }
              ]
            }
          ]
        },
      ]
    }
    

    How would I get an array of all values throughout all nests of this obj by the key of id.

    Note: children is a consistent name, and id's won't exist outside of a children object.

    So from the obj, I would like to produce an array like this:

    const idArray = [1, 2, 3, 4, 5, 6, 7]
    
  • Jack Bashford
    Jack Bashford about 5 years
    No problem @cup_of, I'm glad to help
  • nwales
    nwales over 4 years
    super clean, works well, easy cut & paste. Best answer.
  • Aadam
    Aadam almost 4 years
    InternalError: too much recursion
  • RaaaCode
    RaaaCode almost 4 years
    Have you got an example object?
  • rttmax
    rttmax about 3 years
    Awesome. Exactly what i needed! Works great.
  • Brandon Minton
    Brandon Minton almost 3 years
    This saved me a bit of time and is fun to consider to boot. Thank you!
  • chrwahl
    chrwahl almost 3 years
    It would be helpful if you write an explanation on what you example code does.
  • nanacnote
    nanacnote almost 3 years
    @chrwahl please see the jsdocs comment above code it even comes with examples.
  • luek baja
    luek baja almost 3 years
    Changed line 5 to : (typeof value === 'object' && value) because for some reason, typeof null === "object".
  • Neeraj
    Neeraj over 2 years
    Ah, very clever approach. +1
  • Balram Singh
    Balram Singh almost 2 years
    Best solution so far, thanks a ton Steve