当我发现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结果?
答案 0 :(得分:1)
修正了它(或者可能只是一种解决方法)。
请注意,在游乐场中,Reflect-metadata不可用。属性修饰器可以返回要分配(ORed)到描述符的对象以更改其行为。在角度环境中,反射元数据(特别是Reflect.decorate()
)用于装饰事物。
在阅读reflect-metadata doc和this之后,显然无法在属性修饰器上更改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装饰器,几乎解决了我的用例。希望这有助于某人......