Assigning properties to non-prototype with decorators

I am building a simple mapping between frontend/backend data structure. In order to do that I've created a decorator that looks like the following:

function ApiField(
    apiKey: string,
    setFn: (any) => any = (ret) => ret,
    getFn: (any) => any = (ret) => ret
) {
    return function (target: AbstractModel, propertyKey: string) {
        target.apiFieldsBag = target.apiFieldsBag || {};
        _.assign(
            target.apiFieldsBag,
            {
                [propertyKey]: {
                    apiKey: apiKey,
                    setFn: setFn,
                    getFn: getFn
                }
            }
        );
    };
}

And this is how I use it:

class AbstractCar {
    @ApiField('id')
    public id: string = undefined;
}

class BMW extends AbstractCar {
    @ApiField('cylinders')
    public cylinderCount: number;
}

class VW extends AbstractCar {
    @ApiField('yearCompanyFounded')
    public yearEstablished: number;
}

The issue that I'm seeing is that instead of the actual object being passed to the decorator it's always its prototype:

__decorate([
    ApiField('yearCompanyFounded')
], VW.prototype, "yearEstablished", void 0);

Which means that as I am assigning stuff to the instance in the decorator, it is always attached to the prototype which in turn means that properties I want to be defined only the VW instance are also available on the AbstractCar and the BMW class (in this example this would be yearEstablished). This makes it impossible to have two properties with the same name but different API fields in two different classes.

Is there any way to circumvent this behaviour?

Answers


Right now, all three classes are adding properties to the same object. The key to fix this is to clone the object on target.data so that each class is using a different object instead of all of them referring to the same object.

Here's a simpler example that demonstrates one way of doing this:

function ApiField(str: string) {
    return function (target: any, propertyKey: string) {
        // I tested with Object.assign, but it should work with _.assign the same way
        target.data = _.assign({}, target.data, {
            [propertyKey]: str
        });
    };
}

class AbstractCar {
    @ApiField("car")
    public carID;
}

class BMW extends AbstractCar {
    @ApiField("bmw")
    public bmwID;
}

class VW extends AbstractCar {
    @ApiField("vw")
    public vwID;
}

AbstractCar.prototype.data; // Object {carID: "car"}
BMW.prototype.data;         // Object {carID: "car", bmwID: "bmw"}
VW.prototype.data;          // Object {carID: "car", vwID: "vw"}

The problem is that public inside a class is not standard JavaScript, it’s only something that TypeScript does. Therefore, you have to be careful, because anything you do may break in the future.

One possibility is to use Object.assign() to add instance properties (IINM, apiFieldsBag should be transferred from the object created by the object literal to this):

class AbstractCar {
    constructor() {
        Object.assign(this, {
            @ApiField('id')
            id: undefined,
        });
    }
}

Need Your Help

MATLAB How did I make this loop, infinite + also how to shorten i = i + 1?

matlab loops while-loop infinite

I thought this would be a simple iteration but apparently not, I keep seeing the numbers running up the screen and b = 425.0000 ... which should end my while loop but I have fluffed up royally!