1

I'd like something like Python's defaultdict in Javascript, except without using any libraries. I realize this won't exist in pure Javascript. However, is there a way to define such a type in a reasonable amount of code (not just copying-and-pasting some large library into my source file) that won't hit undesirable corner cases later?

I want to be able to write the following code:

var m = defaultdict(function() { return [] });
m["asdf"].push(0);
m["qwer"].push("foo");
Object.keys(m).forEach(function(value, key) {
  // Should give me "asdf" -> [0] and "qwer" -> ["foo"]
});

I need this to work on recent versions of Firefox, Chrome, Safari, and ideally Edge.

Again, I do not want to use a library if at all possible. I want a way to do this in a way that minimizes dependencies.

Reasons why previous answers don't work:

This answer uses a library, so it fails my initial criteria. Also, the defaultdict it provides doesn't actually behave like a Javascript object. I'm not looking to write Python in Javascript, I'm looking to make my Javascript code less painful.

This answer suggests defining get. But you can't use this to define a defaultdict over collection types (e.g. a map of lists). And I don't think the approach will work with Object.keys either.

This answer mentions Proxy, but it's not obvious to me how many methods you have to implement to avoid having holes that would lead to bad corner cases later. Writing all of the Proxy methods certainly seems like a pain, but if you skip any methods you might cause painful bugs for yourself down the road if you try to use something you didn't implement a handler for. (Bonus question: What is the minimal set of Proxy methods you'd need to implement to avoid any such holes?) On the other hand, the suggested getter approach doesn't follow standard object syntax, and you also can't do things like Object.keys.

Community
  • 1
  • 1
Elliott Slaughter
  • 1,455
  • 19
  • 27
  • This is not intended to answer your question, but it might be useful to think about what you are trying to achieve with this, instead of trying to solve the problem with the way you are familiar with (from another language). – Alex Szabo Feb 14 '17 at 20:27
  • In what way are [defined getters and setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters) insufficent for your purposes? – Hamms Feb 14 '17 at 20:31
  • @AlexSzabó This is an attempt to the make the typical nested map pattern less painful. I.e. I have a type which is conceptually a map `a -> b -> c` and I'd like to be able to write code like `var x = defaultdict(function() { return {}; }); x["a"]["b"] = 1;` without the boilerplate of checking at each level if the next level data structure is there. If there is a better way to do this than I'm suggesting, please enlighten me. – Elliott Slaughter Feb 14 '17 at 21:08
  • @Hamms It looks like the link you pasted was for a getter for a single property, right? I need it to take arbitrary keys. – Elliott Slaughter Feb 14 '17 at 21:11

1 Answers1

4

You really seem to be looking for a proxy. It is available in the modern browsers you mention, is not a library, and is the only technology allowing you to keep the standard object syntax. Using a proxy is actually quite simple, all you need to overwrite is the get trap that should automatically create non-existing properties:

function defaultDict(createValue) {
    return new Proxy(Object.create(null), {
        get(storage, property) {
            if (!(property in storage))
                storage[property] = createValue(property);
            return storage[property];
        }
    });
}

var m = defaultDict(function() { return [] });
m["asdf"].push(0);
m["qwer"].push("foo");
Object.keys(m).forEach(console.log);
Sicco
  • 6,167
  • 5
  • 45
  • 61
Bergi
  • 630,263
  • 148
  • 957
  • 1,375