Skip to content

Commit 824e27e

Browse files
release: 0.2.2 (#252)
2 parents 60aa96c + 4b3e726 commit 824e27e

File tree

4 files changed

+164
-4
lines changed

4 files changed

+164
-4
lines changed

src/TransformOperationExecutor.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export class TransformOperationExecutor {
166166
const metadata = defaultMetadataStorage.findTypeMetadata((targetType as Function), propertyName);
167167
if (metadata) {
168168
const options: TypeHelpOptions = { newObject: newValue, object: value, property: propertyName };
169-
const newType = metadata.typeFunction(options);
169+
const newType = metadata.typeFunction ? metadata.typeFunction(options) : metadata.reflectedType;
170170
if (metadata.options && metadata.options.discriminator && metadata.options.discriminator.property && metadata.options.discriminator.subTypes) {
171171
if (!(value[valueKey] instanceof Array)) {
172172
if (this.transformationType === TransformationType.PLAIN_TO_CLASS) {
@@ -199,6 +199,14 @@ export class TransformOperationExecutor {
199199
this.options.targetMaps
200200
.filter(map => map.target === targetType && !!map.properties[propertyName])
201201
.forEach(map => type = map.properties[propertyName]);
202+
} else if(this.transformationType === TransformationType.PLAIN_TO_CLASS) {
203+
// if we have no registererd type via the @Type() decorator then we check if we have any
204+
// type declarations in reflect-metadata (type declaration is emited only if some decorator is added to the property.)
205+
const reflectedType = Reflect.getMetadata("design:type", (targetType as Function).prototype, propertyName);
206+
207+
if (reflectedType) {
208+
type = reflectedType;
209+
}
202210
}
203211
}
204212

@@ -217,7 +225,7 @@ export class TransformOperationExecutor {
217225
if (newValue.constructor.prototype) {
218226
const descriptor = Object.getOwnPropertyDescriptor(newValue.constructor.prototype, newValueKey);
219227
if ((this.transformationType === TransformationType.PLAIN_TO_CLASS || this.transformationType === TransformationType.CLASS_TO_CLASS)
220-
&& ((descriptor && !descriptor.set) || newValue[newValueKey] instanceof Function)) // || TransformationType === TransformationType.CLASS_TO_CLASS
228+
&& ((descriptor && !descriptor.writable) || newValue[newValueKey] instanceof Function)) // || TransformationType === TransformationType.CLASS_TO_CLASS
221229
continue;
222230
}
223231

src/decorators.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function Transform(transformFn: (value: any, obj: any, transformationType
2222
* Specifies a type of the property.
2323
* The given TypeFunction can return a constructor. A discriminator can be given in the options.
2424
*/
25-
export function Type(typeFunction: (type?: TypeHelpOptions) => Function, options?: TypeOptions) {
25+
export function Type(typeFunction?: (type?: TypeHelpOptions) => Function, options?: TypeOptions) {
2626
return function(target: any, key: string) {
2727
const type = (Reflect as any).getMetadata("design:type", target, key);
2828
const metadata = new TypeMetadata(target.constructor, key, type, typeFunction, options);
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import "reflect-metadata";
2+
import {
3+
plainToClass,
4+
} from "../../src/index";
5+
import {defaultMetadataStorage} from "../../src/storage";
6+
import {Expose, Type} from "../../src/decorators";
7+
import {expect} from "chai";
8+
9+
describe("implicit and explicity type declarations", () => {
10+
11+
defaultMetadataStorage.clear();
12+
13+
class Example {
14+
15+
@Expose()
16+
readonly implicitTypeViaOtherDecorator: Date;
17+
18+
@Type()
19+
readonly implicitTypeViaEmptyTypeDecorator: number;
20+
21+
@Type(() => String)
22+
readonly explicitType: string;
23+
}
24+
25+
const result: Example = plainToClass(Example, {
26+
implicitTypeViaOtherDecorator: "2018-12-24T12:00:00Z",
27+
implicitTypeViaEmptyTypeDecorator: "100",
28+
explicitType: 100,
29+
});
30+
31+
it("should use implicitly defined design:type to convert value when no @Type decorator is used", () => {
32+
expect(result.implicitTypeViaOtherDecorator).to.be.instanceOf(Date);
33+
expect(result.implicitTypeViaOtherDecorator.getTime()).to.be.equal(new Date("2018-12-24T12:00:00Z").getTime());
34+
});
35+
36+
it("should use implicitly defined design:type to convert value when empty @Type() decorator is used", () => {
37+
expect(result.implicitTypeViaEmptyTypeDecorator).that.is.a("number");
38+
expect(result.implicitTypeViaEmptyTypeDecorator).to.be.equal(100);
39+
});
40+
41+
it("should use explicitly defined type when @Type(() => Construtable) decorator is used", () => {
42+
expect(result.explicitType).that.is.a("string");
43+
expect(result.explicitType).to.be.equal("100");
44+
});
45+
46+
});
47+
48+
describe("plainToClass transforms builtin primitive types properly", () => {
49+
50+
defaultMetadataStorage.clear();
51+
52+
class Example {
53+
54+
@Type()
55+
date: Date;
56+
57+
@Type()
58+
string: string;
59+
60+
@Type()
61+
string2: string;
62+
63+
@Type()
64+
number: number;
65+
66+
@Type()
67+
number2: number;
68+
69+
@Type()
70+
boolean: boolean;
71+
72+
@Type()
73+
boolean2: boolean;
74+
}
75+
76+
const result: Example = plainToClass(Example, {
77+
date: "2018-12-24T12:00:00Z",
78+
string: "100",
79+
string2: 100,
80+
number: "100",
81+
number2: 100,
82+
boolean: 1,
83+
boolean2: 0,
84+
});
85+
86+
it("should recognize and convert to Date", () => {
87+
expect(result.date).to.be.instanceOf(Date);
88+
expect(result.date.getTime()).to.be.equal(new Date("2018-12-24T12:00:00Z").getTime());
89+
});
90+
91+
it("should recognize and convert to string", () => {
92+
expect(result.string).that.is.a("string");
93+
expect(result.string2).that.is.a("string");
94+
expect(result.string).to.be.equal("100");
95+
expect(result.string2).to.be.equal("100");
96+
});
97+
98+
it("should recognize and convert to number", () => {
99+
expect(result.number).that.is.a("number");
100+
expect(result.number2).that.is.a("number");
101+
expect(result.number).to.be.equal(100);
102+
expect(result.number2).to.be.equal(100);
103+
});
104+
105+
it("should recognize and convert to boolean", () => {
106+
expect(result.boolean).to.be.true;
107+
expect(result.boolean2).to.be.false;
108+
});
109+
110+
});

test/functional/serialization-deserialization.spec.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,46 @@ describe("serialization and deserialization objects", () => {
107107
// (<any>result).extra.should.be.undefined;
108108
});
109109

110-
});
110+
111+
it("should not overwrite non writable properties on deserialize", () => {
112+
class TestObject {
113+
get getterOnlyProp(): string {
114+
return "I cannot write!";
115+
}
116+
117+
normalProp: string = "Hello!";
118+
}
119+
120+
const payload = {
121+
getterOnlyProp: "I CAN write!",
122+
normalProp: "Goodbye!"
123+
};
124+
125+
const result = deserialize(TestObject, JSON.stringify(payload));
126+
127+
result.getterOnlyProp.should.be.eql("I cannot write!");
128+
result.normalProp.should.be.eql("Goodbye!");
129+
130+
});
131+
132+
it("should overwrite default properties defined in prototype", () => {
133+
class TestObject {
134+
normalProp: string = "Hello!";
135+
prototypedProp: string;
136+
}
137+
138+
TestObject.prototype.prototypedProp = "I'm a BUG!";
139+
140+
141+
const payload = {
142+
normalProp: "Goodbye!",
143+
prototypedProp: "Goodbye!"
144+
};
145+
146+
const result = deserialize(TestObject, JSON.stringify(payload));
147+
148+
result.normalProp.should.be.eql("Goodbye!");
149+
result.prototypedProp.should.be.eql("Goodbye!");
150+
});
151+
152+
});

0 commit comments

Comments
 (0)