打字稿:装饰器在角度项目和打字稿操场

时间:2016-06-09 08:13:57

标签: angularjs json angular-decorator reflect-metadata

当我发现Typescript的私有根本不是私有的时,我需要将一个对象序列化为角度为2.0.0-rc1的json,并且get属性不会通过JSON.stringify输出。

所以我开始装饰这个类:

//method decorator
function enumerable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
}
//property decorator
function exclude(target: any, propertyKey: string): any {
    return { enumerable: false };
}
class MyClass {
    test: string = "test";
    @exclude
    testExclude: string = "should be excluded";
    @enumerable(true)
    get enumerated(): string {
        return "yes";
    }
    @enumerable(false)
    get nonEnumerated(): string {
        return "non enumerable"
    }
}

let x = new MyClass();
//1st
console.log(JSON.stringify(x));
//2nd
console.log(JSON.stringify(x, Object.keys(MyClass.prototype)));
//3rd
console.log(JSON.stringify(x, Object.keys(x).concat(Object.keys(MyClass.prototype))));//test 3
<{3>}上的

,这就是

{"test":"test"}
{"enumerated":"yes"}
{"test":"test","enumerated":"yes"}

但在我的项目(角度2.0.0-rc1)上,这给出了

{"test":"test","testExclude":"should be excluded"}
{"enumerated":"yes"}
{"test":"test","testExclude":"should be excluded","enumerated":"yes"}

我真正追求的是从操场上输出#3。

看一下转换后的代码, 唯一的区别是reflect-metadata的代码:

//snip ...

    __decorate([
        exclude, 
        __metadata('design:type', String)
    ], MyClass.prototype, "testExclude", void 0);
    __decorate([
        enumerable(true), 
        __metadata('design:type', String)
    ], MyClass.prototype, "enumerated", null);
    __decorate([
        enumerable(false), 
        __metadata('design:type', String)
    ], MyClass.prototype, "nonEnumerated", null);
    return MyClass;
}());

在操场上没有__metadata行。

这里发生了什么?我怎样才能在我的项目上获得游乐场的#3结果?

2 个答案:

答案 0 :(得分:1)

修正了它(或者可能只是一种解决方法)。

请注意,在游乐场中,Reflect-metadata不可用。属性修饰器可以返回要分配(ORed)到描述符的对象以更改其行为。在角度环境中,反射元数据(特别是Reflect.decorate())用于装饰事物。

在阅读reflect-metadata docthis之后,显然无法在属性修饰器上更改PropertyDescriptor,因为它与构造函数而不是原型相关联。解决方案(解决方法)是使用新描述符重新创建属性。

function include(value: boolean) {
    return function (target: any, propertyKey: string): any {
        // Buffer the value
        var _val = target[propertyKey];
        // Delete property.
        if (delete target[propertyKey]) {
            // Create new property with getter and setter
            Object.defineProperty(target, propertyKey, {
                get: () => _val,
                set: (newVal) => _val = newVal,
                enumerable: value,
                configurable: true
            });
        }
    }
}

只需要工厂,因此我可以使用@include(false)代替@exclude

唯一的缺点是该属性现在与原型相关联,因此正常JSON.stringify(instance)不会将其序列化。

在这方面,我们可以进一步使通用装饰器在属性和方法中都可用,如下:

//method decorator
function excludeMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = false;
    return descriptor;
};
//property decorator
function excludeProperty(target: any, propertyKey: string): any {
    // Buffer the value
    var _val = target[propertyKey];
    // Delete property.
    if (delete target[propertyKey]) {
        // Create new property with getter and setter
        Object.defineProperty(target, propertyKey, {
            get: () => _val,
            set: (newVal) => _val = newVal,
            enumerable: false,
            configurable: true
        });
    }
}
function exclude(...args : any[]) {
    switch(args.length) {
        case 2:
            return excludeProperty.apply(this, args);
        case 3:
            if (typeof args[2] !== "number")
                return excludeMethod.apply(this, args);
        default:
            throw new Error("Decorators are not valid here!");
    }
}

现在我们可以这样使用它:

class MyClass {
    test: string = "test";
    @exclude
    testExclude: string = "should be excluded";
    get enumerated(): string {
        return "yes";
    }
    @exclude
    get nonEnumerated(): string {
        return "non enumerable"
    }
    constructor() {}
}

let x = new MyClass();
//to serialize, we have to whitelist the instance and its prototype prop keys
console.log(JSON.stringify(x, Object.keys(x).concat(Object.keys(MyClass.prototype))));

到目前为止,我还没有找到更清洁的方法。

答案 1 :(得分:0)

我跌倒兔子洞......

所以出于某种原因,将白名单添加到JSON.stringify以某种方式使它不会递归地序列化嵌套对象:

class a {
    p1 = 1;
    p2 = 2;
}
class b {
    m1 = new a();
    m2 = "test";
    m3 = new Array<a>();
}
let i = new b();
i.m3.push(new a());
i.m3.push(new a());

JSON.stringify(i); 
// properly gives 
// {"m1":{"p1":1,"p2":2},"m2":"test","m3":[{"p1":1,"p2":2},{"p1":1,"p2":2}]}

JSON.stringify(i, Object.keys(i).concat(Object.keys(Object.getPrototypeOf(i))));
// nested class a doesn't get serialized 
// {"m1":{},"m2":"test","m3":[{},{}]}

所以只要把它放在那里,如果你像我一样想要在TS中隐藏私有变量并给它一个只读的外观属性:

将其声明为一个简单的对象成员,然后在构造函数中修改其propertyDescriptor:

//Don't do this
class a {
    private _prop;
    get prop() { return _prop; }
}

//do this instead
class a {
    prop; //just define your public-facing property
    constructor() {
        let _prop; //internal variable here
        Object.defineProperty(this, "prop", { //now we modify the existing prop, 
            get: () =>  _prop, //closure to outside variable 
            //(no set here, it's readonly after all)
            enumerable: true, //make sure it's visible
            configurable: false //close up access
        }); 
    }
}

现在我们可以简单地使用JSON.stringify(instance)。唯一的缺点是如果你有复杂的getter / setter,请记住这是在每个实例/ new中调用的。

上面有这个模式和@exclude装饰器,几乎解决了我的用例。希望这有助于某人......