default values for nested javascript hashes
Asked Answered
E

4

3

In JavaScript I want to do the following:

var pi = {}; pi[0]['*']['*'] = 1;

of course this throws a "Cannot read property '*' of undefined" error. Clearly I can define p[0] = {}, but that's kind of a pain as I will be sticking lots of different values in the different attributes, e.g.

pi[2]['O']['I-GENE'] = 1;

etc. The first key into the hash is just an integer, so I guess I could use an array at the top level instead of a hash, and then default initialize like in this post:

default array values

but that doesn't handle my need for default initialization of the other hashes.

It does seem like what I am trying to do is running up against the ECMAScript spec which indicates that undefined attributes of an object (which is a hash in JavaScript) should return undefined, as mentioned here:

Set default value of javascript object attributes

which interestingly includes a dangerous work around.

In other places where I'm trying use nested hashes like this I am finding myself writing long bits of ugly code like this:

function incrementThreeGramCount(three_grams,category_minus_two,category_minus_one,category){
    if(three_grams[category_minus_two] === undefined){
      three_grams[category_minus_two] = {};
    }
    if(three_grams[category_minus_two][category_minus_one] === undefined){
      three_grams[category_minus_two][category_minus_one] = {};
    }
    if(three_grams[category_minus_two][category_minus_one][category] === undefined){
      three_grams[category_minus_two][category_minus_one][category] = 0;
    }
    three_grams[category_minus_two][category_minus_one][category]++;
}

which I'd really like to avoid here, or at least find some good way of adding to the functionality of the Hash through the prototype method. However it seems like since the Hash and Object in JavaScript are just the same thing, we can't really play around with default hash behaviour in JavaScript without impacting a lot of other things ...

Maybe I should be writing my own Hash class ... or using Prototypes:

http://prototypejs.org/doc/latest/language/Hash/

or someone elses:

http://www.daveperrett.com/articles/2007/07/25/javascript-hash-class/

or mootools:

http://mootools.net/docs/more/Types/Hash

argh, so many choices - wish I knew the best practice here ...

Escutcheon answered 11/4, 2013 at 9:15 Comment(1)
Heh - this is reminding me of a controversial PR on mootools last year: github.com/mootools/mootools-core/pull/2191 - Object.get/set methods (not .prototype) which did that automatically. Did not land and Daniel stopped contributing since :D - his implementation was quite reasonable - github.com/csuwldcat/mootools-core/blob/master/Source/Types/… - can work easily w/o mootoolsArgon
R
2

This can be done using ES6 proxies. You'd define a proxy on an object with a get handler. When a get is performed on a key with an undefined value or for which the object doesn't have an own property, you set it to a new proxy that uses the same get handler and return that new proxy.

Additionally, this would work without the need for the bracket syntax:

var obj = ...;
obj.a.b.c = 3;

Unfortunately, being an ES6 feature, their support is limited to Firefox and they can be enabled in Chrome with an experimental flag.

Radiculitis answered 11/4, 2013 at 9:30 Comment(3)
unfortunately proxy objects are currently only supported by firefox. Maybe you chould put this in as a noteWho
They're supported in Chrome stable with the Enable Experimental JavaScript flag enabled as they've been in V8 for a very long time. I'm sure when the spec is finalized they'll be enabled in the next stable push.Radiculitis
Nice, Thx I didn't knew chrome supports proxies as experimental +1Who
A
1

I would do something like that:

Object.prototype.get = function(v){ 
    if(!this[v]){
        this[v] = {} 
    } 

    return this[v];  
}

and then, instead object["x"]["y"].z = 666 use object.get("x").get("y").z = 666.

Aldridge answered 11/4, 2013 at 9:25 Comment(3)
this looks cool, but doesn't it have dangerous side effects on other libraries that might be relying on the default get behaviour that returns 'undefined'? What I'd love is this mod, but restricted to the local code - hence a Hash class seems safer, no?Escutcheon
well, there is a chance some lib also defines get method, so try use more unique name like get_or_initialize... or consider less verbose name like _ :)Aldridge
ah got you - sorry I missed that you were recommending changing the syntax to the .get("x") - yeah, wish I could stick with ['x'] so much less verbose - I guess ._('x') is not bad too. And any custom Hash Class I made would have to change syntax too - I guess I can't easily override the [] operator like Object.prototype.[] = function(v){ ...Escutcheon
W
0

You could write a simple helper to do this. e.g

function setWDef() {
    var args = [].slice.call(arguments);
    var obj = args.shift();
    var _obj = obj;
    var val = args.pop();
    if (typeof obj !== "object") throw new TypeError("Expected first argument to be of type object");
    for (var i = 0, j = args.length - 1; i < j; i++) {
        var curr = args[i];
        if (!_obj[curr]) _obj[curr] = {};
        _obj = _obj[curr];
    }
    _obj[args.pop()] = val;
    return obj;
}   

Now just set the value using the function

var pi ={}
console.log (setWDef(pi,"0","*","a*",1)) //{"0": {"*": {"a*": 1}}}

Heres a Demo on JSBin

Who answered 11/4, 2013 at 9:36 Comment(0)
V
0

With the drafted logical OR assignment operator ||= you can do the following:

 const pi = [];
 ((((pi[0]||={})['*'])||={})['*'] = 1);
 console.log(JSON.stringify({pi}));

Output:

{"pi":[{"*":{"*":1}}]}

You can also initialize some levels as arrays or define default property values:

const pi = [];
((((((pi[0]||={})['*'])||=[])[3])||={'#':2})['*'] = 1);
console.log(JSON.stringify({pi}));

Output:

{"pi":[{"*":[null,null,null,{"#":2,"*":1}]}]}
Veridical answered 27/2, 2021 at 18:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.