Is it possible to get the non-enumerable inherited property names of an object?
Asked Answered
D

11

134

In JavaScript we have a few ways of getting the properties of an object, depending on what we want to get.

1) Object.keys(), which returns all own, enumerable properties of an object, an ECMA5 method.

2) a for...in loop, which returns all the enumerable properties of an object, regardless of whether they are own properties, or inherited from the prototype chain.

3) Object.getOwnPropertyNames(obj) which returns all own properties of an object, enumerable or not.

We also have such methods as hasOwnProperty(prop) lets us check if a property is inherited or actually belongs to that object, and propertyIsEnumerable(prop) which, as the name suggests, lets us check if a property is enumerable.

With all these options, there is no way to get a non-enumerable, non-own property of an object, which is what I want to do. Is there any way to do this? In other words, can I somehow get a list of the inherited non-enumerable properties?

Thank you.

Desorb answered 5/11, 2011 at 23:38 Comment(2)
Your question answered the question I was going to ask: How to inspect non-enumerable properties (just to explore what is available in predefined objects). Finally I found getOwnPropertyNames! :-)Discombobulate
@Discombobulate :-) That's what SO is all about!Desorb
O
149

Since getOwnPropertyNames can get you non-enumerable properties, you can use that and combine it with walking up the prototype chain.

function getAllProperties(obj){
    var allProps = []
      , curr = obj
    do{
        var props = Object.getOwnPropertyNames(curr)
        props.forEach(function(prop){
            if (allProps.indexOf(prop) === -1)
                allProps.push(prop)
        })
    }while(curr = Object.getPrototypeOf(curr))
    return allProps
}

console.log(getAllProperties([1,2,3]));

I tested that on Safari 5.1 and got

> getAllProperties([1,2,3])
["0", "1", "2", "length", "constructor", "push", "slice", "indexOf", "sort", "splice", "concat", "pop", "unshift", "shift", "join", "toString", "forEach", "reduceRight", "toLocaleString", "some", "map", "lastIndexOf", "reduce", "filter", "reverse", "every", "hasOwnProperty", "isPrototypeOf", "valueOf", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "propertyIsEnumerable", "__lookupSetter__"]

Update: Refactored the code a bit (added spaces, and curly braces, and improved the function name):

function getAllPropertyNames(obj) {
  var props = [];

  do {
    Object.getOwnPropertyNames(obj).forEach(function (prop) {
      if (props.indexOf(prop) === -1) {
        props.push(prop);
      }
    });

    obj = Object.getPrototypeOf(obj);
  } while (obj);

  return props;
}
Orts answered 6/11, 2011 at 0:13 Comment(14)
Thanks toby, one thing I don't understand is the line: while(curr = Object.getPrototypeOf(cure)), as the conditional statement uses an assignment operator instead of a comparison operator, wouldn't this always return true? Or is this line essentially checking whether "curr" has a prototype?Desorb
@AlexNabokov it will return false if the result is falsy, which will occur when Object.getPrototypeOf(cure) returns null at the top of the prototype chain. I guess this assumes no circular prototype chains!Bobinette
@Bobinette All prototype chains end with the null value. The do-while loop iterates over every object in the prototype chain and ends when null is reached. Btw prototype chains can never be circular (since you cannot modify the implicit prototype link of an object after it has been created).Rhythmandblues
@ŠimeVidas you seem to be correct; I thought you could do something weird with constructor functions but after hacking away in the console for 10 minutes can't seem to make anything circular happen.Bobinette
@Alex Function.prototype can never be the "root" prototype, since it's prototype link points to Object.prototype. The function Object.getPrototypeOf( obj ) returns the topmost object in the prototype chain of obj. It enables you to follow the prototype chain of obj until you reach its end (the null value). I'm not sure what your issue with this is...Rhythmandblues
@ŠimeVidas For example, if I create a constructor: function Boy() { this.height = "tall" } and then an instance of it: John = new Boy(), and do Object.getPrototypeOf(John), I will get undefined. But if I do Object.getPrototypeOf(obj).constructor recursively, it will correctly return the chain John -> Boy -> Object. Only then can I capture the properties of each "chainling" in the chain.Desorb
@ŠimeVidas PS I'm new to this so could be way off :) but isn't the topmost object in the prototype chain always Object()? I thought Object.getPrototypeOf(obj) will return the obj's constructor's prototype, which is immediately before it in the chain.Desorb
@Alex No, it's not undefined. Object.getPrototypeOf(John) returns the Boy.prototype object (as it should) - see here: jsfiddle.net/aeGLA/1. Note that the constructor Boy is not in the prototype chain of John. The prototype chain of John is as follows: Boy.prototype -> Object.prototype -> null.Rhythmandblues
"I thought Object.getPrototypeOf(obj) will return the obj's constructor's prototype" - Yes. In the case of John, his constructor is Boy, and the prototype property of Boy is Boy.prototype. So Object.getPrototypeOf(John) returns Boy.prototype.Rhythmandblues
@ŠimeVidas Aaah OK my understanding of what a prototype chain is was a bit off then :) Thanks for bearing with my noob-ness.Desorb
Doesn't this answer need to de-dupe the property names to have clean behavior (say if you want to iterate through all the properties of an object)?Ainslie
Instead of props = props.concat(x) use props.apply(props, x)Highness
This will error out if the argument provided is null or undefined.Bitty
Props (pun intended, for fun ;-) ) to this answer as it has inspired some of Mozilla's documentation: developer.mozilla.org/en-US/docs/Web/JavaScript/…Kauri
O
20

A cleaner solution using recursion:

function getAllPropertyNames (obj) {
    const proto     = Object.getPrototypeOf(obj);
    const inherited = (proto) ? getAllPropertyNames(proto) : [];
    return [...new Set(Object.getOwnPropertyNames(obj).concat(inherited))];
}

Edit

More generic functions:

function walkProtoChain (obj, callback) {
    const proto     = Object.getPrototypeOf(obj);
    const inherited = (proto) ? walkProtoChain(proto, callback) : [];
    return [...new Set(callback(obj).concat(inherited))];
}

function getOwnNonEnumPropertyNames (obj) {
    return Object.getOwnPropertyNames(obj)
        .filter(p => !obj.propertyIsEnumerable(p));
}

function getAllPropertyNames (obj) {
    return walkProtoChain(obj, Object.getOwnPropertyNames);
}

function getAllEnumPropertyNames (obj) {
    return walkProtoChain(obj, Object.keys);
}

function getAllNonEnumPropertyNames (obj) {
    return walkProtoChain(obj, getOwnNonEnumPropertyNames);
}

This same template can be applied using Object.getOwnPropertySymbols, etc.

Orpine answered 5/3, 2019 at 16:7 Comment(0)
K
15

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:

  1. 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,
  2. a condition to walk the prototype chain or not (per airportyh's answer), and,
  3. 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.

Kauri answered 8/1, 2022 at 3:47 Comment(6)
Criminally underrated answer (1 other upvote as of writing). Should probably add an Ecmascript version warning though (e.g. when does it stop working: ES5/ES6/ES7)Flam
@JeffHykin, Thanks! (for the feedback, and the edit). Admittedly, I'm not certain what you mean by your "when does it stop working" point. I think you mean "what's the earliest version of ES that it works in (whereas it fails in any version previous to that)"...? I think the newest functionality it uses is the Reflect.ownKeys(), but I can't seem to find anything on when that was introduced. I'd welcome any assistance with this.Kauri
Welcome! And yes, my comment should say "when does it start working". Reflect is from ES6, and I'm confident thats the only version-dependent feature for getAllKeys. For the getAllKeysConditionally, arrow functions are also ES6 and default values are ES5. So overall this should work for ES6 and newer 👍Flam
Thanks for the welcome and the info! 😊 Added ES6+ to the heading, and a note at the end.Kauri
A--Option 1 will include duplicate keys. That's why the other ES6 answers use Sets. B--For the record, my answer does mention that it can be used to get Symbols, etc., albeit as a footnote. For example, walkProtoChain(obj, Reflect.ownKeys) will retrieve all keys. C--"no one else here has used Array.prototype.filter()" I did ;) That said, most answers don't use it because it's not necessary. The question was about string properties, of which almost any subset can be directly queried without any filtering.Orpine
A-- Good point (duplicate keys), with sets as the solution, although what if I want ALL keys, somehow, even the duplicates (ie. I want some record of the duplicates, though I'm not sure what would be the appropriate representation of that)? I can think of a couple of ideas to solve that, but now getting even more beyond the scope of the original question. 😊 B. Yes, fair enough. C. Fair enough again. Apologies, I missed your use of filter. 😊Kauri
K
5

Straight forward iterative in ES6:

function getAllPropertyNames(obj) {
    let result = new Set();
    while (obj) {
        Object.getOwnPropertyNames(obj).forEach(p => result.add(p));
        obj = Object.getPrototypeOf(obj);
    }
    return [...result];
}

Example run:

function getAllPropertyNames(obj) {
  let result = new Set();
  while (obj) {
    Object.getOwnPropertyNames(obj).forEach(p => result.add(p));
    obj = Object.getPrototypeOf(obj);
  }
  return [...result];
}

let obj = {
  abc: 123,
  xyz: 1.234,
  foobar: "hello"
};

console.log(getAllPropertyNames(obj));
Kaslik answered 15/12, 2019 at 4:35 Comment(0)
D
4

Taking advantage of Sets leads to a somewhat cleaner solution, IMO.

const own = Object.getOwnPropertyNames;
const proto = Object.getPrototypeOf;

function getAllPropertyNames(obj) {
    const props = new Set();
    do own(obj).forEach(p => props.add(p)); while (obj = proto(obj));
    return Array.from(props);
}
Defluxion answered 6/10, 2017 at 18:39 Comment(0)
C
2

if you are trying to log non enumerable properties of a parent object ex. by default the methods defined inside a class in es6 are set on prototype but are set as non-enumerable.

Object.getOwnPropertyNames(Object.getPrototypeOf(obj));
Colossal answered 20/7, 2017 at 7:14 Comment(0)
S
1

To get all inherited properties or methods for some instance you could use something like this

var BaseType = function () {
    this.baseAttribute = "base attribute";
    this.baseMethod = function() {
        return "base method";
    };
};

var SomeType = function() {
    BaseType();
    this.someAttribute = "some attribute";
    this.someMethod = function (){
        return "some method";
    };
};

SomeType.prototype = new BaseType();
SomeType.prototype.constructor = SomeType;

var instance = new SomeType();

Object.prototype.getInherited = function(){
    var props = []
    for (var name in this) {  
        if (!this.hasOwnProperty(name) && !(name == 'constructor' || name == 'getInherited')) {  
            props.push(name);
        }  
    }
    return props;
};

alert(instance.getInherited().join(","));
Synchronize answered 6/11, 2011 at 0:7 Comment(4)
Better to use Object.getInherited rather than Object.prototype.getInherited. Doing that also removes the need for the ugly !(name == 'getInherited') check. Also, in your implementation, the props array can contain duplicate properties. Lastly, what's the purpose of ignoring the constructor property?Naive
When will object.getInherited will become true? Please check below question as I am stuck with inheritance: #31718845Identic
IMHO - these belong to Reflect, not to Object. Or - alternatively - I'd expect from the language Object.keys(src, [settings]) where optional settings can specify if to include non-ninumerables, if to include inheritted, if to include non-enumerable inheritted, if to include own, if to include symbols, and perhaps to what max inheritance depth to dig.Bigford
uh... same for Object.entries. Not sure about Object.values though. ...well. why not.Bigford
G
1

You usually don't want to include Object prototype properties such as __defineGetter__, hasOwnProperty, __proto__ and so on.

This implementation allows you to either include or exclude Object prototype properties:

function getAllPropertyNames(object, includeObjectPrototype = false) {
  const props = Object.getOwnPropertyNames(object);

  let proto = Object.getPrototypeOf(object);
  const objectProto = Object.getPrototypeOf({});

  while (proto && (includeObjectPrototype || proto !== objectProto)) {
    for (const prop of Object.getOwnPropertyNames(proto)) {
      if (props.indexOf(prop) === -1) {
        props.push(prop);
      }
    }
    proto = Object.getPrototypeOf(proto);
  }

  return props;
}

console.log(getAllPropertyNames(new Error('Test'), true));
// ["fileName", "lineNumber", "columnNumber", "message", "toString", "name", "stack", "constructor", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__"]
console.log(getAllPropertyNames(new Error('Test'), false));
// [ "fileName", "lineNumber", "columnNumber", "message", "toString", "name", "stack", "constructor" ]
Gaynor answered 6/10, 2021 at 6:10 Comment(0)
C
0

Here is the solution that I came up with while studying the subject. To get all non-enumerable non-own properties of the obj object do getProperties(obj, "nonown", "nonenum");

function getProperties(obj, type, enumerability) {
/**
 * Return array of object properties
 * @param {String} type - Property type. Can be "own", "nonown" or "both"
 * @param {String} enumerability - Property enumerability. Can be "enum", 
 * "nonenum" or "both"
 * @returns {String|Array} Array of properties
 */
    var props = Object.create(null);  // Dictionary

    var firstIteration = true;

    do {
        var allProps = Object.getOwnPropertyNames(obj);
        var enumProps = Object.keys(obj);
        var nonenumProps = allProps.filter(x => !(new Set(enumProps)).has(x));

        enumProps.forEach(function(prop) {
            if (!(prop in props)) {
                props[prop] = { own: firstIteration, enum_: true };
            }           
        });

        nonenumProps.forEach(function(prop) {
            if (!(prop in props)) {
                props[prop] = { own: firstIteration, enum_: false };
            }           
        });

        firstIteration = false;
    } while (obj = Object.getPrototypeOf(obj));

    for (prop in props) {
        if (type == "own" && props[prop]["own"] == false) {
            delete props[prop];
            continue;
        }
        if (type == "nonown" && props[prop]["own"] == true) {
            delete props[prop];
            continue;
        }

        if (enumerability == "enum" && props[prop]["enum_"] == false) {
            delete props[prop];
            continue;
        }
        if (enumerability == "nonenum" && props[prop]["enum_"] == true) {
            delete props[prop];
        }
    }

    return Object.keys(props);
}
Cobblestone answered 17/1, 2016 at 1:2 Comment(0)
C
0
function getNonEnumerableNonOwnPropertyNames( obj ) {
    var oCurObjPrototype = Object.getPrototypeOf(obj);
    var arReturn = [];
    var arCurObjPropertyNames = [];
    var arCurNonEnumerable = [];
    while (oCurObjPrototype) {
        arCurObjPropertyNames = Object.getOwnPropertyNames(oCurObjPrototype);
        arCurNonEnumerable = arCurObjPropertyNames.filter(function(item, i, arr){
            return !oCurObjPrototype.propertyIsEnumerable(item);
        })
        Array.prototype.push.apply(arReturn,arCurNonEnumerable);
        oCurObjPrototype = Object.getPrototypeOf(oCurObjPrototype);
    }
    return arReturn;
}

Example of using:

function MakeA(){

}

var a = new MakeA();

var arNonEnumerable = getNonEnumerableNonOwnPropertyNames(a);
Colchis answered 2/11, 2016 at 14:37 Comment(0)
D
0

An implementation in my personal preferences :)

function getAllProperties(In, Out = {}) {
    const keys = Object.getOwnPropertyNames(In);
    keys.forEach(key => Object.defineProperty(In, key, {
        enumerable: true
    }));
    Out = { ...In, ...Out };

    const Prototype = Object.getPrototypeOf(In);
    return Prototype === Object.prototype ? Out : getAllProperties(Proto, Out);
}
Disposal answered 11/1, 2020 at 10:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.