So you want the length
of something like {0: "first", 100: "last"}
to be 2
and not 101
, right? That is, the length
of something like a sparse array is expected to be less than the value you'd get from its length
property. There's no built-in method that will do that for you, so filtering properties is probably the easiest way to go.
Assuming so:
A better filter for "numeric" keys (which as you know are really strings) is something like this:
Object.keys(this).filter(k => (""+(+k))===k).length
The filter is coercing the key to a number and then back to a string... if it survives that roundtrip unscathed, then that key is indistinguishable from a numeric key. Otherwise it is not a true "numeric" key and you won't be able to access that property with a numeric index. For example:
const obj: { [k: number]: string | undefined } = {};
obj[1] = "A";
console.log(JSON.stringify(obj)); // {"1":"A"}
obj[+1] = "B";
console.log(JSON.stringify(obj)); // {"1":"B"}
obj["1"] = "C";
console.log(JSON.stringify(obj)); // {"1":"C"}
obj["+1"] = "D";
console.log(JSON.stringify(obj)); // {"1":"C","+1":"D"}
console.log(Object.keys(obj).filter(o => !isNaN(parseInt(o, 10))));
// Array [ "1", "+1" ]
console.log(Object.keys(obj).filter(k => ("" + (+k)) === k));
// Array [ "1" ]
Even though the numeric value 1
is the same as the numeric value +1
, the string value "1"
is distinct from the string value "+1"
. The object obj
has properties at keys "1"
and "+1"
but you can only access the "1"
property with a numeric index.
Note that a numeric index also doesn't restrict you to nonnegative whole number keys. All of the following is fine in TypeScript and in JavaScript:
obj[1.5] = "E";
obj[-2] = "F";
obj[Infinity] = "G";
obj[NaN] = "H";
obj[1.23e100] = "I";
And most of those fail the parseInt()
test but pass the filter I've given. Similarly, keys like "23 skidoo"
would fail the filter I've given but pass the parseInt()
test.
Anyway, hope that helps. Good luck!
UPDATE: since you want this to act more like an actual array, the length
property should always be greater than the highest numerical index. It looks like an array specifically only cares about numeric-compatible indices that correspond to non-negative integers less than some value (2^32 maybe). You can get something like that behavior with the following:
get length() {
return Math.max(-1, ...Object.keys(this).filter(
k => ("" + (+k)) === k // numeric keys
).map(
k => +k // as numbers
).filter(
n => isFinite(n) && n >= 0 && n === Math.round(n) // non-neg integers
&& n < 4294967296 // less than 2^32
)) + 1;
}
You could simplify that a bit but it's more or less what you're looking for, I think.
If you'd rather come at this whole problem from a different angle you could make your class instances a Proxy
that delegates numeric index accesses and length
to a real array. Or maybe better, you can make your class a subclass of Array
itself to inherit the behavior of length
without having to reimplement it from scratch.
Good luck again.