0

If I have an array

let ar = ['a', 'b', 'c'];
ar.prop1 = 'Hello';
ar.prop2 = 'world';

How do I iterate over just the properties (prop1 and prop2) in this case?

8
  • 2
    Why do you use an array for this purpose? Use an object instead. Arrays are used as indexed collections, objects as keyed collections. You’re trying to use an array for both purposes at the same time, but want to enumerate only the keyed part. That’s just asking for bugs. Commented Mar 24, 2021 at 23:56
  • Does this answer your question? How to loop through an array containing objects and access their properties Commented Mar 24, 2021 at 23:57
  • @MVB76 No, the array in question does not contain objects. Commented Mar 24, 2021 at 23:57
  • 2
    There is not a simple way to separate out the array entries from the property names. This is a bad/wrong use of an array. I you want to store separate properties, then create a parent object, put the properties on that parent object and put the array into a property of the parent object. Then, you have them separate. Commented Mar 25, 2021 at 0:07
  • 1
    The only hack I know of is to use Object.getOwnProperties() and then filter out the numeric property names. You can see why code shouldn't be written this way because it takes a hack to separate them. Commented Mar 25, 2021 at 0:08

2 Answers 2

3

I'm not aware of any way to directly iterate just the plain properties. But, you can construct a list of just the plain properties as follows:

ar.keys() gives you only the array element indexes (not plain properties)

Object.keys(ar) gives you all properties and array indexes.

So, you can start with all the properties and indexes and then filter out the ones that are array element indexes and be left with just the properties.

let ar = ['a', 'b', 'c'];
ar.prop1 = 'Hello';
ar.prop2 = 'world';

let arrayIndexes = new Set(Array.from(ar.keys(), i => "" + i));
console.log("arrayIndexes (converted to string)", Array.from(arrayIndexes));

let props = Object.keys(ar).filter(k => !arrayIndexes.has(k));
console.log("props", props);

Or, the same concept with fewer temporary objects:

let ar = ['a', 'b', 'c'];
ar.prop1 = 'Hello';
ar.prop2 = 'world';

let arrayIndexes = new Set();
for (let i of ar.keys()) {
    arrayIndexes.add("" + i);
}
console.log("arrayIndexes (converted to string)", Array.from(arrayIndexes));

let props = Object.keys(ar).filter(k => !arrayIndexes.has(k));
console.log("props", props);

If you want all own properties, including non-enumerable properties, you could do this:

let ar = ['a', 'b', 'c'];
ar.prop1 = 'Hello';
ar.prop2 = 'world';

let arrayIndexes = new Set();
for (let i of ar.keys()) {
    arrayIndexes.add("" + i);
}
console.log("arrayIndexes (converted to string)", Array.from(arrayIndexes));

let props2 = Object.keys(Object.getOwnPropertyDescriptors(ar)).filter(k => !arrayIndexes.has(k));
console.log("props2", props2);

Examining Characters in Property Name

Note: I attempted a solution that iterates all the properties (including array indexes) and filters out things that will be interpreted as array indexes by examining the characters in the property name. This proved to be troublesome. It's not just as simple as whether it's a numeric value or not or converts to a number. To be considered an array index, here's what I've discovered so far:

  1. It must contain only digits 0-9 (no plus or minus sign)
  2. It must pass Number.isSafeInteger() because it can be a number, but too large to be an integer without some loss of precision. When converted to a number, it must also be less than ar.length.
  3. The property name must not start with a "0" unless it's only "0". For example x["01"] will not be treated as an array index equivalent to x[1].

Here are some numeric-like strings that are not interpreted as array indexes:

"01"
"00"
"9999999999999999999999999999999"
"-1"
"-0"
"1.0"

Here's is such a test, though I don't find it simpler than the above tests:

let ar = ['a', 'b', 'c'];
ar.prop1 = 'Hello';
ar.prop2 = 'world';
ar["-1"] = 'minus one';
ar["0"] = 'a';
ar["-0"] = 'z';
ar["01"] = 'zzzz';
ar["1.0"] = 'aaa';
ar["999"] = 'Z';
ar["99999999999999999999999999999999"] = 'ZZ';
ar["+0"] = 'bbb';

let props = Object.keys(ar).filter(k => {
    // a property that isn't "0", but starts with "0" will not be an array index
    // for example, "01" is not an array index
    if (k.length > 1 && k.startsWith("0")) {
        return true;
    }
    // if not entirely made up of digits 0-9, keep it
    if (!/^\d+$/.test(k)) {
        return true;
    }
    // convert to number
    let index = +k;
    // if it's not a safe integer or it's longer than the length,
    // then it must not be an array index
    if (!Number.isSafeInteger(index) || index >= ar.length) {
        return true;
    }
    // if it passed all these tests, then it must be an array index so filter it out
    return false;

});
console.log("props", props);

Sign up to request clarification or add additional context in comments.

Comments

2

You can extract the keys from the array portion and identify the non-configurable properties.

let ar = ['a', 'b', 'c'];
ar.prop1 = 'Hello';
ar.prop2 = 'world';

const keys = [...ar.keys()].map(k => `${k}`);
console.log(
  Object.getOwnPropertyNames(ar).filter(n => {
    const desc = Object.getOwnPropertyDescriptor(ar, n);
    return desc.enumerable && desc.configurable && !keys.includes(n);
  }),
);

1 Comment

Clever! This is guaranteed to work because Array.prototype.keys creates an iterator based only on numeric integer indexes from 0 to length − 1.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.