Update Jan 2022 -- almost every answer combined, plus symbols (ES6+)
After seeing Mozilla's JS documentation specifically say: "no single mechanism iterates all of an object's properties; the various mechanisms each include different subsets of properties."... I had exactly this question albeit newer because I want symbol keys also and I think all the answers above pre-date those).
I came here hoping someone else knew how to create such a single mechanism.
No single answer on this page seems to cover EVERYTHING, but between all of them, I think it can be done -- including the option to also exclude the annoying top level keys.
In exploring the code in Mozilla's JS doc'n that airportyh's answer inspired, plus the table below it on that same page I discovered the new Reflect.ownKeys
. That catches everything (including symbols)... except inherited properties, but airportyh's answer walking the prototype chain fixes that.
So... combining all those findings and simplifying as much as I could, I came up with the following two functions, that (I believe) DO catch EVERYTHING. Posting in case it helps anyone else.
Option 1. Simple: Return EVERY Key, no exceptions
Returns every key, enumerable or not, string, symbol, own, inherited, and top level.
function getAllKeys(obj) {
let keys = [];
// if primitive (primitives still have keys) skip the first iteration
if (!(obj instanceof Object)) {
obj = Object.getPrototypeOf(obj)
}
while (obj) {
keys = keys.concat(Reflect.ownKeys(obj));
obj = Object.getPrototypeOf(obj);
}
return keys;
}
I really like the simplicity, though I wonder if I've missed anything. If any one catches any errors in that, please do let me know.
Option 2. Flexible: Return EVERY Key, with optional exclusions
Adds:
- a filter function based on a bunch of one line functions (easier to debug that way, and this ain't code golf 😉) that determine if any given key should be excluded, based on the parameters passed in,
- a condition to walk the prototype chain or not (per airportyh's answer), and,
- a condition to stop or not before the top level is reached (per Maciej Krawczyk's answer).
Include or exclude:
- enumerable keys
- non-enumerable keys
- symbol keys
- string keys
- own keys
- inherited keys
- top level keys.
(On a side note, I'm no JS expert, so maybe I'm missing something. I'm a little confused why no one else here has used Array.prototype.filter(), since isn't that exactly what we're doing?)
I believe the following covers it. By default everything is included except top level keys. Adjust to taste. Again I'd welcome feedback if any errors here:
function getAllKeysConditionally(obj, includeSelf = true, includePrototypeChain = true, includeTop = false, includeEnumerables = true, includeNonenumerables = true, includeStrings = true, includeSymbols = true) {
// Boolean (mini-)functions to determine any given key's eligibility:
const isEnumerable = (obj, key) => Object.propertyIsEnumerable.call(obj, key);
const isString = (key) => typeof key === 'string';
const isSymbol = (key) => typeof key === 'symbol';
const includeBasedOnEnumerability = (obj, key) => (includeEnumerables && isEnumerable(obj, key)) || (includeNonenumerables && !isEnumerable(obj, key));
const includeBasedOnKeyType = (key) => (includeStrings && isString(key)) || (includeSymbols && isSymbol(key));
const include = (obj, key) => includeBasedOnEnumerability(obj, key) && includeBasedOnKeyType(key);
const notYetRetrieved = (keys, key) => !keys.includes(key);
// filter function putting all the above together:
const filterFn = key => notYetRetrieved(keys, key) && include(obj, key);
// conditional chooses one of two functions to determine whether to exclude the top level or not:
const stopFn = includeTop ? (obj => obj === null) : (obj => Object.getPrototypeOf(obj) === null);
// and now the loop to collect and filter everything:
let keys = [];
while (!stopFn(obj, includeTop)) {
if (includeSelf) {
const ownKeys = Reflect.ownKeys(obj).filter(filterFn);
keys = keys.concat(ownKeys);
}
if (!includePrototypeChain) { break; }
else {
includeSelf = true;
obj = Object.getPrototypeOf(obj);
}
}
return keys;
}
As noted by Jeff Hykin in the comments these solutions use Reflect
and arrow functions which are new in ES6. Therefore ES6 minimum required.