I stacked with similar question and wrote this code :
const dynamicFunc = (input) => {
const handler = {
get: (obj, prop) => {
// edge case when .toString() is calling for dynamic prop - just return any string
if (typeof prop === 'symbol') {
return () => "custom_dynamic_str";
}
const isPropExist = typeof obj[prop] !== 'undefined';
if (isPropExist) {
const val = obj[prop];
// if null or undefined was set
if (val == null || typeof val === 'undefined') {
return val;
}
// if value was created not by this method - return value
if (!val.__isDynamic) {
return val;
}
return dynamicFunc(val);
}
obj[prop] = () => dynamicFunc({});
obj[prop].__isDynamic = true;
return dynamicFunc(obj[prop]);
},
set(target, prop, value) {
// if our custom function was set to dynamic
if (typeof value === 'function') {
// wrap it to unwrap in get
target[prop] =
{
__isSetAsFunction: true,
func: value
};
}
else {
target[prop] = value;
}
return true;
},
apply: function (target, thisArg, argumentsList) {
return dynamicFunc({});
}
};
let proxy = new Proxy(input, handler);
return proxy;
};
return dynamicFunc(baseObj);
i use Proxy class as a base solution here, so in two words - every time you are trying to access some property it creates this property if it's not already there. it's time to time edited, because i stacked with issues and it's was need to solve them, so as is :)
Sample:
let ob = dynamic();
ob.test1.test2.test3('hello from Belarus!','qwerty').test5.t('test');
//we could assign properties on the fly :
ob.myProp.pyProp2 = 2;
console.log(ob.myProp.pyProp2) // "1" will be outputed
// some tests using chai and chai-spies:
dynamic().genericProperties.anyFunc().myprop.test();
let obj = dynamic({ predefinedObjValue: 3 });
obj.prop1.prop2.test = 1;
obj.prop1.prop3.test = 2;
obj.prop2.myfunc = () => { };
expect(obj.prop1.prop2.test).to.be.equal(1);
expect(obj.prop1.prop3.test).to.be.equal(2);
expect(obj.predefinedObjValue).to.be.equal(3);
const myFuncSpy = chai.spy.on(obj.prop2, 'myfunc');
obj.prop2.myfunc();
expect(myFuncSpy).to.have.been.called();
and it will no cause any errors
also, you can define your own base object and it will not be overrided :
let ob = dynamic({mycustomProp : 1});
ob.test1.test2.test3('asdasd','tt').test5.t('test'); //without error
console.log(ob.mycustomProp) // "1" will be outputed
Working sample : https://codesandbox.io/s/cranky-liskov-myf65