Auto-Vivification in JavaScript (upgraded)

While I am an architect and even after 35 years, I love core coding, a lot of times I go into this “academic” developers mode where I tinker with stuff, get envious of another language and try to replicate it in a different language.

Like the other day someone (Manas Dash) showed me how to create quick enums in Python

RED, GREEN, BLUE = range(3)

I got so jealous that I started tinkering with JavaScript and landed up creating 3 different versions of range – for loop – yield, recursive function yield and finally Array expansion yield (You can find thos posts on this blog).

Recently, the same person asked me “Do you know auto-vivification?” and took me back a few years when I was tinkering with perl, so it launched me again into “academician” mode and I wrote auto-vivification in JavaScript and Python (see earlier posts).

All this was just academic interest but then someone said “Hey! You AutoViv doesn’t work in my chart application”. I sat up and said “Hain?”… I didn’t expect anyone to take it seriously. It was just a proof of concept, but then here was someone who was taking it more seriously than I was.

// Use Proxy object and handler to create a new property on the fly
let Auto = () =>
// Proxy() object of JavaScript
new Proxy({}, {
// Handler checks if prop exits
// otherwise, creates it
get: (obj, prop) =>
prop in obj
? obj[prop]
: obj[prop] = Auto()
}
)
// Create only TOP level
let univ = Auto();
// Now assign to deeper level
univ.college.stream.year = "A+";
// Bingo! It works
console.log(univ);
view raw Auto.js hosted with ❤ by GitHub
AutoVivification

Now someone was using this code and told me that many 3rd party libraries were failing because of these Proxy objects, and also wanted to use Array vivification

Something like this

var univ = Auto();
univ.college.stream.year = 2020;
// This is not Auto, its array
univ.college.course = []
univ.college.course[0] = 'Computers'
univ.college.classes = []
univ.college.classes[0] = {name:'Commerce',strength:30}
// Not Auto, so doesn't work
univ.college.classes[2].name = 'Electronics'
Auto doesn’t work on Array

This made me go back to my tinkering in Non-theoretical Engineer mode (Sorry, Sheldon!)
I had to add 2 things to my original code

  1. Convert Proxy graph back to Plain Old JavaScript object graph
  2. Add support for array vivification
Here is what is looks like now
// Needed to check isProxy
const util = require('util');
// Quick and Dirty Type Finder
const type = (obj) =>
obj.__proto__.constructor.name;
// Switch-offable debug
debug = console.log;
// Helpers
const isArray = (obj)
=> type(obj) == "Array";
const isObject = (obj)
=> type(obj) == "Object";
const isFunction = (obj)
=> type(obj) == "Function";
const isProxy = (obj)
=> util.types.isProxy(obj);
const isComplex = (obj) =>
isArray(obj) ||
isObject(obj) ||
isFunction(obj) ||
isProxy(obj)
const isSimple = (obj)
=> !isComplex(obj)
// Convert back to POJSO
// Deep clone Auto object
function deepClone(inputObject) {
// Do we have a leaf element?
if (isSimple(inputObject))
return inputObject;
// We have either array or nested object
let outputObject =
type(inputObject) == "Array"
? []
: {};
// If it's an array
// Even empty slots of Array(n)
if (isArray(inputObject)) {
for (let element = 0;
element < inputObject.length;
element++) {
let value = inputObject[element];
// Either recursively clone array/object
// Or simply copy native types
outputObject[element] =
isArray(inputObject) || isObject(inputObject)
? deepClone(value)
: value;
}
}
// If it's an object
else {
for (let element in inputObject) {
let value = inputObject[element];
outputObject[element] =
isArray(inputObject) || isObject(inputObject)
? deepClone(value)
: value;
}
}
// We are ready with POJSO
return outputObject;
}
// Support added for Array vivification
// If elements is non 0, it creates an array of objects
// otherwise creates a single object
function Auto(elements) {
// Convert to plain old javascript object
function toObject() {
return deepClone(this);
}
if (elements != undefined && elements > 0) {
// If called as Auto(n)
// Create array on n AutoVivs
return new Proxy(new Array(elements),
{
get: (obj, prop) => {
if (prop == 'toObject')
return toObject;
if (prop >= obj.length)
return undefined;
if (obj[prop] === undefined)
obj[prop] = Auto();
return obj[prop];
}
}
)
} else {
// If called as Auto()
// Create a single AutoViv object
return new Proxy({},
{
// Handler checks if prop does not exists, creates it
get: (obj, prop) => {
if (prop == 'toObject')
return toObject;
return (prop in obj
? obj[prop]
: obj[prop] = Auto());
}
}
)
}
}
let univ = Auto();
univ.college.stream.year = "A+";
univ.college.stream.subjects = Auto(3);
univ.college.stream.subjects[0].name = "JavaScript";
univ.college.stream.subjects[0].marks = 33;
console.log(univ);
console.log(univ.toObject());
console.log(univ.college.stream.subjects);
console.log(univ.college.stream.subjects.toObject());
/* Output
{ college: { stream: { year: 'A+', subjects: [Array] } } }
{ college: { stream: { year: 'A+', subjects: [Array] } } }
[ { name: 'JavaScript', marks: 33 }, {}, {} ]
[ { name: 'JavaScript', marks: 33 }, {}, {} ]
*/
view raw Auto.js hosted with ❤ by GitHub
Auto with Clone

The guy who is using is seriously said “Cool, it works!” and later said “Oh it works for nested and array too”.

This is still of academic interest to me. I haven’t built this as commercial piece of code or encouraging people to start using Auto-vivification. While it can be convenient, it can introduce more problems in the code with typos

Imagine landing up doing
univ.collage.steam.year = "A+"
and then wonder why it’s not working, because there will be NO errors with auto-vivification. Whatever you do WILL be accepted.

Still, if you want to give this a spin and use it in your side project, let me know how it goes.

Auto Vivification Series

  1. Auto vivification in JavaScript
  2. Auto vivification in Python
  3. Auto vivification in JavaScript (Upgraded)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s