DEV Community

Cover image for Autovivification in JavaScript
Aleksandar Aleksandrov
Aleksandar Aleksandrov

Posted on

Autovivification in JavaScript

Autovivification is a very long and idiosyncratic word, which simply means that each time you dereference an undefined value, automatically an object is created in it's place.

Hmm... that wasn't very clear, was it? Let see an example to get the idea, trust me it's quite simple 😊

In JavaScript you can easily do this

const non_vivified = {};
non_vivified.prop = 'value';
Enter fullscreen mode Exit fullscreen mode

but can not do this

const non_vivified = {};
non_vivified.parent_prop.child_prop = 'value';
> Uncaught TypeError: non_vivified.parent_prop is undefined
Enter fullscreen mode Exit fullscreen mode

And this is the case in the vast majority of programming languages. We usually prefer this approach as the latter is considered the less expected behavior.

Now one place where this is not the case is in Perl. Perl has autovivification by default and is by far the most popular example of this property (even the picture on this post shows it).

OK, now let's get back to JavaScript. Where would we like such behavior to be present πŸ€” Well the most obvious answer is when you want to dynamically create big nested structures as it would save you the hassle of checking if the property exists on each step and creating it.

Alright we are almost there now, how do we add autovivification to our js objects. It's actually quite simple, we use the Proxy object that was added in ES6.

Let's first see the code and work backwards.

function autovivify() {
    return new Proxy({}, {
        get: (target, name) => {
            if (name === 'toJSON') {
                return () => target;
            } else {
                return name in target ?
                    target[name] :
                    target[name] = autovivify()
            }
        }
    })
}
Enter fullscreen mode Exit fullscreen mode

What we just did is create a function that returns a new instance of a vivified object. The important part is the second object passed to the Proxy constructor. It's basically an object that describes the handlers of the Proxy. As you guessed get is the handler that is invoked when we request a property of the object. In our case if it does not exist we simply create a new Proxy object by recursively calling the autovivify function. If it exists, then simply return the property. The other important thing is the toJSON check. This is done so that we do not get a stack overflow error when calling JSON.stringify as this is the method being called when serialization happens.

Now we can do

const vivified = autovivify();
vivified.parent_prop.child_prop = 'value';
Enter fullscreen mode Exit fullscreen mode

This is all nice and fine, but what if I want to vivify an already existing object. Well that is somewhat harder than expected, as we want proxies to be invisible to the caller. Or as the ES spec describes it

"It is impossible to determine whether an object is a proxy or not (transparent virtualization)."

Your best bet would be to traverse the object, create a vivified object for each object in the source and when you hit a leaf, i.e a non object value just assign it.

All of this is best explained with some code.

// for convenience here, make the autovivify function accept a starting object as well
function autovivify(obj = {}) {
   return new Proxy(obj, { ...

// copy from the source
function vivify(source) {
// isArray and isObject are simple utils
    const result = util.isArray(source) ? autovivify([]) : autovivify();
    for (const key in source) {
        if (util.isObject(source[key])) {
            result[key] = vivify(source[key])
        } else {
            result[key] = source[key]
        }
    }
    return result;
}
Enter fullscreen mode Exit fullscreen mode

Now you can do stuff like

const vivified = vivify({ p1: { p2: [] } })
console.log(vivified) // { p1: { p2: [] } }
console.log(vivified.p1.p2) // []
vivified.p1.p3.p4.p5.p6 = 2 // create a new deep property assignment
console.log(vivified.p1.p3.p4.p5.p6) // 2
Enter fullscreen mode Exit fullscreen mode

An important limitation/feature here is that if you once assign a value to a property, vivification will not kick in as intended. For example:

const v = autovivify();
v.p1.p2 = 2;
v.p1.p2.p3.p4 = 4;
> Uncaught TypeError: Cannot set property 'p4' of undefined
Enter fullscreen mode Exit fullscreen mode

Overall vivification is a strange property to most non Perl developers that with a little proxy magic can be achieved in JavaScript as well. Use it wisely...

Latest comments (0)