0

I feel like the answer to this is a hard no in most languages I've used other than PHP, which at least used to have some odd corner cases with stuff like $someArray['nonexistentKey']++.

I'm basically looking to write a sparse object where each property is a numeric counter. The counters should return 0 if undefined and should automatically define themselves with a value of 0 if you try to increment them.

In other words, I want to override the generic Object getter to return 0 in all cases where it would return undefined... or perhaps define the property right there during the get and set it to 0.

So in theory, an overload for ANY property not yet defined would get initialize it at zero. For example this:

myObj['hugePrimeNumberToBaseXToString']++;

would then make it 1.

In the olden days I feel like some way with Object.__proto__ might have worked for this case...

joshstrike
  • 1,753
  • 11
  • 15
  • Does this answer your question? [Defaultdict equivalent in javascript](https://stackoverflow.com/questions/19127650/defaultdict-equivalent-in-javascript) – Brian McCutchon Jan 04 '21 at 06:53

2 Answers2

3

I think what you want is a Proxy.

You can use a proxy to intercept property gets and return your own logic. In this case, return zero in the case of an undefined property.

// Hold the data
const target: { [key in string]: number } = {}

// Access the data
const proxy = new Proxy(target, {
    get(target, prop, receiver) {
        if (typeof prop === 'string') {
            if (target[prop] === undefined) return 0
            return target[prop]
        }
        return undefined
    }
})

proxy['hugePrimeNumberToBaseXToString']++
console.log(proxy['hugePrimeNumberToBaseXToString']) //=> 1

Playground

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • 1
    Whoa. I've been doing this for ages and written all kinds of gnarly ways to check if objects had been externally manipulated, short of object.watch(), and I didn't know you could do that at a fundamental level. Thank you. – joshstrike Jan 04 '21 at 06:59
1

Proxy is definitely the right answer, but I'd argue that tinkering with Object directly is much farther under the hood than you want to be.

Instead, I'd make a new, simple object type like so:

const Dictionary = function() {}

Dictionary.prototype.add = function(key) {
  if (this.hasOwnProperty(key)) {
    this[key] += 1;
  } else {
    this[key] = 1;
  }
}

const dict = new Dictionary();

dict.add("apples");

console.log(dict.apples) // 1

dict.add("apples");
dict.add("bananas");

console.log(dict.apples, dict.bananas) // 2, 1

It's not quite what you wanted since you have to call add each time, but I'd take the three-character tradeoff for the sake of simplicity and extensibility.

Codepen

Chris Wilson
  • 6,599
  • 8
  • 35
  • 71
  • 1
    I like this because it's AS3-like. Also answers the self-reference question but doesn't get to the immediate getter/setter of any property the way I was hoping for. Ultimately I was trying to create TypeScript classes like this that would accept only certain key types, so this could be a better way to go, because after messing around I don't think TypeScript allows you to extend Proxy in that way. Anyway thank you for the alternative answer. Good thought process here. – joshstrike Jan 04 '21 at 08:53
  • Yeah, I was thinking along the lines of Python's [`.get`](https://www.tutorialspoint.com/python/dictionary_get.htm) method for dictionaries, which allows for a default value if none exists -- would be trivial to precisely recreate that. – Chris Wilson Jan 04 '21 at 16:20