1

Code:

const obj = {
  client: {
    id: 1,
    personal: {
      name: "Mike"
    }
  },
  address: {
    street: "streetname"
  }
};

function recursiveKeys(obj) {
  Object.keys(obj).forEach((key) => {
    if (typeof obj === "object") {
      Object.keys(obj[key]).forEach((innerKey) => {
        console.log(`${key}.${innerKey}`);
      });
    }
  });
}

recursiveKeys(obj);

Desired output:

client.id
client.personal.name
address.street

This code works only for a 2 level object, but it won't work for a 3rd-4th level and deeper, is there a clean way to achieve this?

Alexander Kim
  • 17,304
  • 23
  • 100
  • 157

3 Answers3

4

You need to make your recursiveKeys actually recursive. Pass along the partial property string from the parent object on each recursive call.

const obj = {
  client: {
    id: 1,
    personal: {
      name: "Mike"
    }
  },
  address: {
    street: "streetname"
  }
};

function recursiveKeys(obj, propStr = '') {
  Object.entries(obj).forEach(([key, val]) => {
    const nestedPropStr = propStr + (propStr ? '.' : '') + key;
    if (typeof val === 'object') recursiveKeys(val, nestedPropStr);
    else console.log(nestedPropStr);
  });
}
recursiveKeys(obj);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
2

The answer is: recursion!

const obj = {
  client: {
    id: 1,
    personal: {
      name: "Mike"
    }
  },
  address: {
    street: "streetname"
  }
};

function recursiveKeys(obj) {
  const keys = []
  Object.keys(obj).forEach((key) => {
    if (typeof obj[key] === "object" && obj[key]) {
    //vvvvvvvvvvvvvvvvvvvvvvv--- the function calls itself
      recursiveKeys(obj[key]).forEach(innerKey => {
        keys.push(`${key}.${innerKey}`)
      })
    }else{
      keys.push(key)
    }
  });
  return keys
}

console.log(recursiveKeys(obj));

If you have access to the new Array#flatMap() method, you can use it to make this even more elegant:

const obj = {
  client: {
    id: 1,
    personal: {
      name: "Mike"
    }
  },
  address: {
    street: "streetname"
  }
};

function recursiveKeys(obj) {
  return Object.keys(obj).flatMap(key => 
    typeof obj[key] === "object" && obj[key]
      ? recursiveKeys(obj[key]).map(innerKey => `${key}.${innerKey}`)
      : key
  );
}

console.log(recursiveKeys(obj));
FZs
  • 16,581
  • 13
  • 41
  • 50
2

This code works only for a 2 level object, but it won't work for a 3rd-4th level and deeper, is there a clean way to achieve this?

The problem is you should make your recursiveKeys as it is with 3 steps:

  1. Determine the key result named keyRes
  2. Check if the inner content is an object, then recursive it.
  3. Print the keyRes along with getting out of the recursive, important to avoid infinite loop !!!

const obj = {
  client: {
    id: 1,
    personal: {
      name: "Mike"
    }
  },
  address: {
    street: "streetname"
  }
};

function recursiveKeys(obj, previousKey = '') {
  Object.entries(obj).forEach(([key, values]) => {
    let keyRes = previousKey ? `${previousKey}.${key}` : key; // Step 1 
    if (typeof values === 'object')  // Step 2
      recursiveKeys(values, keyRes); 
    else // Step 3
      console.log(keyRes); 
  });

}

recursiveKeys(obj);
Nguyễn Văn Phong
  • 13,506
  • 17
  • 39
  • 56
  • Yeah, thanks, why you chose `Object.entries()` instead of `Object.keys()`? – Alexander Kim Jan 29 '21 at 09:06
  • 1
    Because I have to use both `key` and `value` of each `object`. `key` to determine `keyRes` in `Step 1`, `value` to use for `Step 2` to make it as **recursive**. Read [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) for more details about `Object.entries()`. – Nguyễn Văn Phong Jan 29 '21 at 09:07
  • ah, i see, thanks. – Alexander Kim Jan 29 '21 at 09:12
  • The [answer of @CertainPerformance](https://stackoverflow.com/a/65950507/9071943) is good. So you should mark his answer as resolved by clicking the tick to help the reader in the future :D – Nguyễn Văn Phong Jan 29 '21 at 09:13