7

Is there anyway, either natively or through a library, to use autovivification on Javascript objects?

IE, assuming foo is an object with no properties, being able to just do foo.bar.baz = 5 rather than needing foo.bar = {}; foo.bar.baz = 5.

James McMahon
  • 48,506
  • 64
  • 207
  • 283
  • 4
    Purely natively, I don't think so. `undefined` isn't extensible or changeable and that's about the only way I could think of doing it without passing it through a function. – Snuffleupagus Nov 21 '12 at 17:08
  • 2
    No. See http://stackoverflow.com/questions/7691395/autovivification-and-javascript – lbstr Nov 21 '12 at 17:09
  • @Ibstr, that question, while referencing autovivification and JavaScript, is not asking the same thing I am. – James McMahon Nov 21 '12 at 18:29
  • @Snuffleupagus, thanks I didn't think there was a way to do it natively, but it is good to have some confirmation. You should make your comment into an answer. – James McMahon Nov 21 '12 at 18:32
  • To the people voting to close this question on the basis of duplication, this is not a duplicate of question http://stackoverflow.com/questions/7691395/autovivification-and-javascript, that question is asking about the nature of autovivification. I am asking if there is are any libraries to accomplish autovivification in Javascript. It is a completely different question which should be evident to anyone who actually reads rubixibuc's question. I've edited the question title to be clearer about what I am asking for. – James McMahon Nov 21 '12 at 23:47

5 Answers5

6

You can't do it exactly with the syntax you want. But as usual, in JS you can write your own function:

function set (obj,keys,val) {
    for (var i=0;i<keys.length;i++) {
        var k = keys[i];
        if (typeof obj[k] == 'undefined') {
            obj[k] = {};
        }
        obj = obj[k];
    }
    obj = val;
}

so now you can do this:

// as per you example:
set(foo,['bar','baz'],5);

without worrying if bar or baz are defined. If you don't like the [..] in the function call you can always iterate over the arguments object.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • I'm setting dictionary values like set('dict_obj',[0,'User'],5). But I cannot return the value I set using a call like "Console.log('dict_obj[0]['User']) I can't return the value 5. Do you see any error with how I'm using this? – ouonomos Dec 09 '13 at 05:10
  • @ouonomos: You're doing it wrong, you're setting the value on the string `'dict_obj'` instead of the object `dict_obj`. – slebetman Dec 09 '13 at 05:23
  • I apologize; I mistyped. What I'm actually doing is: lastposxns={}; grpname=0; tempname='abc'; ypos=5; dict_set(lastposxns[grp,tempname],ypos); where dict_set is the autoviv function. But console.log(lastposxns[0]['abc']) doesn't return the value 5. – ouonomos Dec 09 '13 at 05:34
  • @ouonomos: Still wrong, in javascript, using the comma in a square bracket access operator (as opposed to square bracket array literal) is interpreted as a comma operator. So your code is equivalent to: `lastposxns[tempname]` which is incidentally undefined. Also, you're calling the function as `dict_set(undefined,ypos,undefined)`. The first undefined is because of the reason I mentioned, the second undefined is because you're not supplying the value. What this means is that you're trying to set the property named `5` (ypos) of undefined (lastposxns[tempname] which is lastposxns.abc) – slebetman Dec 09 '13 at 05:55
  • @ouonomos: Correct syntax should be: `dict_set(lastposxns,[grpname,tempname],ypos)` – slebetman Dec 09 '13 at 05:57
2

Purely natively, I don't think so. undefined isn't extensible or changeable and that's about the only way I could imagine doing it without passing it through a function.

Snuffleupagus
  • 6,365
  • 3
  • 26
  • 36
2

I had a desire to do this, so I wrote a package to handle it.

% npm install autovivify
% node
> Av = require('autovivify')
> foo = new Av()
{}
> foo.bar.baz = 5
5
> foo
{ bar: { baz: 5 } }
>

It'll even do arrays with numeric subscripts:

> foo = new Av()
> foo.bar.baz[0] = 'hum'
> foo
{ bar: { baz: [ 'hum' ] } }
Allen Luce
  • 7,859
  • 3
  • 40
  • 53
0

Or you can use an eval-based solution. It's ugly, not recommended.

function av(xpr) {

    var res = "";
    var pos = 0;
    while (true) {

        var pos = xpr.indexOf("[",pos);
        if (pos == -1) break;
        var frag = xpr.substr(0,pos);
        pos++;

        res += "if (typeof(" + frag + ") != 'object') " + frag + " = {};\n";
    } // while

    return res + xpr;
} // av()


function main() {

    var a = {};
    a["keep"] = "yep";
    eval(av('a[1][1]["xx"] = "a11xx";   '));
    eval(av('a[1][2]["xx"] = "a12xx";   '));

    console.log(a);
} // main()
ern0
  • 3,074
  • 25
  • 40
0

@slebetman's code doesn't seem to work. The last key should not be assigned an empty object, but rather the val. This code worked:

function autoviv(obj,keys,val) {
  for (var i=0; i < keys.length; i++) {
    var k = keys[i];
    if (typeof obj[k] === 'undefined') {
      if(i === keys.length-1) {
        obj[k] = val;
        return;
      }
      obj[k] = {};
    }
    obj = obj[k];
  }
}

foo = {}
autoviv(foo,['bar','baz'],5);
console.log(foo.bar.baz);
5
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • Hi Mark , i have doubt , is there any good method to capture screen shot using angular js , if yes could you please check this https://stackoverflow.com/questions/44602309/making-a-proper-image-capture-of-screen-using-jquery – Abilash Erikson Jun 17 '17 at 08:27