I've been exploring a method to create an @enumerable decorator that can expose properties set through accessor methods.
It's quite simple to achieve this for instances of a class:
// This function works well when called in the class constructor like makeEnumerable(this, ['prop1', 'prop2'])
const makeEnumerable = (what: any, props: string[]) => {
for (const property of props) {
const descriptor = Object.getOwnPropertyDescriptor(what.constructor.prototype, property);
if (descriptor) {
const modifiedDescriptor = Object.assign(descriptor, { enumerable: true });
Object.defineProperty(what, property, modifiedDescriptor);
}
}
};
However, transforming this into a decorator seems challenging because the function lacks access to the instance.
// Doesn't work with Object.keys, Object.getOwnPropertyNames, or Object.entries
function enumerable (value: boolean = true): any {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): any {
if (descriptor) {
Object.assign(descriptor, { enumerable: value });
}
};
}
The property still can be enumerated within for (const x in y)
constructs (oddly), but not elsewhere - and unfortunately, Object.entries
throws an error.
Below is an example using the aforementioned functions:
class MyClass {
#privateVal1: any;
#privateVal2: any;
constructor () {
makeEnumerable(this, ['b']);
}
@enumerable(true)
get a () {
return this.#privateVal1;
}
set a (val: any) {
this.#privateVal1 = val;
}
get b () {
return this.#privateVal2;
}
set b (val: any) {
this.#privateVal2 = val;
}
}
const enumerableA = new MyClass();
enumerableA.a = 5;
enumerableA.b = 6;
const keys = [];
for (const key in enumerableA) {
keys.push(key);
}
console.log({
'forin': keys, // ['a', 'b']
'keys': Object.keys(enumerableA), // ['b']
'keys(proto)': Object.keys(Object.getPrototypeOf(enumerableA)), // ['a']
'getOwnPropertyNames': Object.getOwnPropertyNames(enumerableA), // ['b']
'getOwnPropertyNames(proto)': Object.getOwnPropertyNames(Object.getPrototypeOf(enumerableA)), // ['constructor', 'a', 'b']
});
console.log({
'entries': Object.entries(enumerableA), // Error('Cannot read private member #privateVal1 from an object whose class did not declare it');
'entries(proto)': Object.entries(Object.getPrototypeOf(enumerableA)), // Error('Cannot read private member #privateVal1 from an object whose class did not declare it');
});
Is there a way to utilize a decorator to transform an accessor method into an enumerable property?