Autovivification in JavaScript
Tags: javascript, proxy, perl
Published: 2020-12-26 22:14:12

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
```js
const non_vivified = {};
non_vivified.prop = 'value';
```
but can not do this
```js
const non_vivified = {};
non_vivified.parent_prop.child_prop = 'value';
> Uncaught TypeError: non_vivified.parent_prop is undefined
```

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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) object that was added in ES6. 

Let's first see the code and work backwards.
```js
function autovivify() {
    return new Proxy({}, {
        get: (target, name) => {
            if (name === 'toJSON') {
                return () => target;
            } else {
                return name in target ?
                    target[name] :
                    target[name] = autovivify()
            }
        }
    })
}
```

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
```js
const vivified = autovivify();
vivified.parent_prop.child_prop = 'value';
``` 

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.
```js
// 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;
}
```
Now you can do stuff like
```js
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
```

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:
```js
const v = autovivify();
v.p1.p2 = 2;
v.p1.p2.p3.p4 = 4;
> Uncaught TypeError: Cannot set property 'p4' of undefined
```

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...