Skip to content

Commit 3e01791

Browse files
Merge branch 'develop'
2 parents 824e27e + 530e583 commit 3e01791

7 files changed

+158
-50
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
11
# Changelog and release notes
22

3+
4+
### 0.2.3 [BREAKING CHANGE]
5+
6+
#### Changed
7+
8+
- `enableImplicitConversion` has been added and imlplicit value conversion is disabled by default.
9+
- reverted #234 - fix: write properties with defined default values on prototype which broke the `@Exclude` decorator.
10+
11+
### 0.2.2 [BREAKING CHANGE]
12+
13+
> **NOTE:** This version is deprecated.
14+
15+
This version has introduced a breaking-change when this library is used with class-validator. See #257 for details.
16+
17+
#### Added
18+
19+
- implicity type conversion between values.
20+
321
### 0.2.1
422

23+
> **NOTE:** This version is deprecated.
24+
525
#### Added
626

727
- add option to strip unkown properties via using the `excludeExtraneousValues` option

README.md

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Class-transformer allows you to transform plain object to some instance of class
1111
Also it allows to serialize / deserialize object based on criteria.
1212
This tool is super useful on both frontend and backend.
1313

14-
Example how to use with angular 2 in [plunker](http://plnkr.co/edit/Mja1ZYAjVySWASMHVB9R).
14+
Example how to use with angular 2 in [plunker](http://plnkr.co/edit/Mja1ZYAjVySWASMHVB9R).
1515
Source code is available [here](https://github.com/pleerock/class-transformer-demo).
1616

1717
## What is class-transformer
@@ -103,7 +103,7 @@ Here is example how it will look like:
103103
```typescript
104104
fetch("users.json").then((users: Object[]) => {
105105
const realUsers = plainToClass(User, users);
106-
// now each user in realUsers is instance of User class
106+
// now each user in realUsers is instance of User class
107107
});
108108
```
109109

@@ -247,7 +247,7 @@ let photos = deserializeArray(Photo, photos);
247247

248248
## Enforcing type-safe instance
249249

250-
The default behaviour of the `plainToClass` method is to set *all* properties from the plain object,
250+
The default behaviour of the `plainToClass` method is to set *all* properties from the plain object,
251251
even those which are not specified in the class.
252252

253253
```typescript
@@ -274,7 +274,7 @@ console.log(plainToClass(User, fromPlainUser))
274274
// }
275275
```
276276

277-
If this behaviour does not suit your needs, you can use the `excludeExtraneousValues` option
277+
If this behaviour does not suit your needs, you can use the `excludeExtraneousValues` option
278278
in the `plainToClass` method while *exposing all your class properties* as a requirement.
279279

280280
```typescript
@@ -297,7 +297,7 @@ console.log(plainToClass(User, fromPlainUser, { excludeExtraneousValues: true })
297297
// User {
298298
// id: undefined,
299299
// firstName: 'Umed',
300-
// lastName: 'Khudoiberdiev'
300+
// lastName: 'Khudoiberdiev'
301301
// }
302302
```
303303

@@ -309,7 +309,7 @@ Since Typescript does not have good reflection abilities yet,
309309
we should implicitly specify what type of object each property contain.
310310
This is done using `@Type` decorator.
311311

312-
Lets say we have an album with photos.
312+
Lets say we have an album with photos.
313313
And we are trying to convert album plain object to class object:
314314

315315
```typescript
@@ -532,7 +532,7 @@ In this case you don't need to `@Exclude()` a whole class.
532532

533533
## Skipping private properties, or some prefixed properties
534534

535-
If you name your private properties with a prefix, lets say with `_`,
535+
If you name your private properties with a prefix, lets say with `_`,
536536
then you can exclude such properties from transformation too:
537537

538538
```typescript
@@ -563,7 +563,7 @@ export class User {
563563
get name() {
564564
return this.firstName + " " + this.lastName;
565565
}
566-
566+
567567
}
568568

569569
const user = new User();
@@ -750,8 +750,8 @@ export class Photo {
750750
}
751751
```
752752

753-
Now when you call `plainToClass` and send a plain representation of the Photo object,
754-
it will convert a date value in your photo object to moment date.
753+
Now when you call `plainToClass` and send a plain representation of the Photo object,
754+
it will convert a date value in your photo object to moment date.
755755
`@Transform` decorator also supports groups and versioning.
756756

757757
### Advanced usage
@@ -765,7 +765,7 @@ The `@Transform` decorator is given more arguments to let you configure how you
765765
| Argument | Description
766766
|--------------------|---------------------------------------------------------------------------------|
767767
| `value` | The property value before the transformation.
768-
| `obj` | The transformation source object.
768+
| `obj` | The transformation source object.
769769
| `type` | The transformation type.
770770

771771
## Other decorators
@@ -799,7 +799,7 @@ class User {
799799
}
800800

801801
class UserController {
802-
802+
803803
@TransformClassToPlain({ groups: ['user.email'] })
804804
getUser() {
805805
const user = new User();
@@ -825,6 +825,31 @@ Once TypeScript team provide us better runtime type reflection tools, generics w
825825
There are some tweaks however you can use, that maybe can solve your problem.
826826
[Checkout this example.](https://github.com/pleerock/class-transformer/tree/master/sample/sample4-generics)
827827

828+
## Implicit type conversion
829+
830+
> **NOTE** If you use class-validator together with class-transformer you propably DON'T want to enable this function.
831+
832+
Enables automatic conversion between built-in types based on type information provided by Typescript. Disabled by default.
833+
834+
```ts
835+
import { IsString } from 'class-validator'
836+
837+
class MyPayload {
838+
839+
@IsString()
840+
prop: string
841+
}
842+
843+
844+
const result1 = plainToClass(MyPayload, { prop: 1234 }, { enableImplicitConversion: true });
845+
const result2 = plainToClass(MyPayload, { prop: 1234 }, { enableImplicitConversion: false });
846+
847+
/**
848+
* result1 will be `{ prop: "1234" }` - notice how the prop value has been converted to string.
849+
* result2 will be `{ prop: 1234 }` - default behaviour
850+
*/
851+
```
852+
828853
## How does it handle circular references?
829854

830855
Circular references are ignored.
@@ -851,7 +876,7 @@ this.http
851876

852877
You can also inject a class `ClassTransformer` as a service in `providers`, and use its methods.
853878

854-
Example how to use with angular 2 in [plunker](http://plnkr.co/edit/Mja1ZYAjVySWASMHVB9R).
879+
Example how to use with angular 2 in [plunker](http://plnkr.co/edit/Mja1ZYAjVySWASMHVB9R).
855880
Source code is [here](https://github.com/pleerock/class-transformer-demo).
856881

857882
## Samples

src/ClassTransformOptions.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,11 @@ export interface ClassTransformOptions {
6767
* This option is useful when you know for sure that your types might have a circular dependency.
6868
*/
6969
enableCircularCheck?: boolean;
70+
71+
/**
72+
* If set to true then class transformer will try to convert properties implicitly to their target type based on their typing information.
73+
*
74+
* DEFAULT: `false`
75+
*/
76+
enableImplicitConversion?: boolean;
7077
}

src/TransformOperationExecutor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,11 @@ 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) {
202+
} else if(this.options.enableImplicitConversion && this.transformationType === TransformationType.PLAIN_TO_CLASS) {
203203
// if we have no registererd type via the @Type() decorator then we check if we have any
204204
// type declarations in reflect-metadata (type declaration is emited only if some decorator is added to the property.)
205205
const reflectedType = Reflect.getMetadata("design:type", (targetType as Function).prototype, propertyName);
206-
206+
207207
if (reflectedType) {
208208
type = reflectedType;
209209
}
@@ -225,7 +225,7 @@ export class TransformOperationExecutor {
225225
if (newValue.constructor.prototype) {
226226
const descriptor = Object.getOwnPropertyDescriptor(newValue.constructor.prototype, newValueKey);
227227
if ((this.transformationType === TransformationType.PLAIN_TO_CLASS || this.transformationType === TransformationType.CLASS_TO_CLASS)
228-
&& ((descriptor && !descriptor.writable) || newValue[newValueKey] instanceof Function)) // || TransformationType === TransformationType.CLASS_TO_CLASS
228+
&& ((descriptor && !descriptor.set) || newValue[newValueKey] instanceof Function)) // || TransformationType === TransformationType.CLASS_TO_CLASS
229229
continue;
230230
}
231231

test/functional/implicit-type-declarations.spec.ts

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,55 @@ import {defaultMetadataStorage} from "../../src/storage";
66
import {Expose, Type} from "../../src/decorators";
77
import {expect} from "chai";
88

9+
describe("implicit type conversion", () => {
10+
it("should run only when enabled", () => {
11+
defaultMetadataStorage.clear();
12+
13+
class SimpleExample {
14+
15+
@Expose()
16+
readonly implicitTypeNumber: number;
17+
18+
@Expose()
19+
readonly implicitTypeString: string;
20+
}
21+
22+
const result1: SimpleExample = plainToClass(SimpleExample, {
23+
implicitTypeNumber: "100",
24+
implicitTypeString: 133123,
25+
}, { enableImplicitConversion: true });
26+
27+
const result2: SimpleExample = plainToClass(SimpleExample, {
28+
implicitTypeNumber: "100",
29+
implicitTypeString: 133123,
30+
}, { enableImplicitConversion: false });
31+
32+
expect(result1).to.deep.equal({ implicitTypeNumber: 100, implicitTypeString: "133123" });
33+
expect(result2).to.deep.equal({ implicitTypeNumber: "100", implicitTypeString: 133123 });
34+
});
35+
});
36+
937
describe("implicit and explicity type declarations", () => {
1038

1139
defaultMetadataStorage.clear();
1240

1341
class Example {
14-
42+
1543
@Expose()
1644
readonly implicitTypeViaOtherDecorator: Date;
1745

1846
@Type()
1947
readonly implicitTypeViaEmptyTypeDecorator: number;
20-
21-
@Type(() => String)
48+
49+
@Type(() => String)
2250
readonly explicitType: string;
2351
}
24-
25-
const result: Example = plainToClass(Example, {
26-
implicitTypeViaOtherDecorator: "2018-12-24T12:00:00Z",
27-
implicitTypeViaEmptyTypeDecorator: "100",
28-
explicitType: 100,
29-
});
52+
53+
const result: Example = plainToClass(Example, {
54+
implicitTypeViaOtherDecorator: "2018-12-24T12:00:00Z",
55+
implicitTypeViaEmptyTypeDecorator: "100",
56+
explicitType: 100,
57+
}, { enableImplicitConversion: true });
3058

3159
it("should use implicitly defined design:type to convert value when no @Type decorator is used", () => {
3260
expect(result.implicitTypeViaOtherDecorator).to.be.instanceOf(Date);
@@ -45,14 +73,14 @@ describe("implicit and explicity type declarations", () => {
4573

4674
});
4775

48-
describe("plainToClass transforms builtin primitive types properly", () => {
76+
describe("plainToClass transforms built-in primitive types properly", () => {
4977

5078
defaultMetadataStorage.clear();
5179

5280
class Example {
5381

5482
@Type()
55-
date: Date;
83+
date: Date;
5684

5785
@Type()
5886
string: string;
@@ -61,27 +89,27 @@ describe("plainToClass transforms builtin primitive types properly", () => {
6189
string2: string;
6290

6391
@Type()
64-
number: number;
92+
number: number;
6593

6694
@Type()
6795
number2: number;
68-
96+
6997
@Type()
7098
boolean: boolean;
71-
99+
72100
@Type()
73101
boolean2: boolean;
74102
}
75-
76-
const result: Example = plainToClass(Example, {
77-
date: "2018-12-24T12:00:00Z",
78-
string: "100",
79-
string2: 100,
103+
104+
const result: Example = plainToClass(Example, {
105+
date: "2018-12-24T12:00:00Z",
106+
string: "100",
107+
string2: 100,
80108
number: "100",
81109
number2: 100,
82110
boolean: 1,
83111
boolean2: 0,
84-
});
112+
}, { enableImplicitConversion: true });
85113

86114
it("should recognize and convert to Date", () => {
87115
expect(result.date).to.be.instanceOf(Date);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// import { expect } from "chai";
2+
3+
// import { plainToClass } from "../../src";
4+
5+
// describe("Prototype inheritance", () => {
6+
7+
// // https://github.com/typestack/class-transformer/issues/233
8+
// it("should set value if property has default value in prototype chain.", () => {
9+
// class TestObject {
10+
// normalProp: string = "Hello!";
11+
// prototypedProp: string;
12+
// }
13+
14+
// TestObject.prototype.prototypedProp = "I'm a BUG!";
15+
16+
// const payload = {
17+
// normalProp: "Goodbye!",
18+
// prototypedProp: "Goodbye!"
19+
// };
20+
21+
// const result = plainToClass(TestObject, payload);
22+
23+
// expect(result).to.eql({
24+
// normalProp: "Goodbye!",
25+
// prototypedProp: "Goodbye!"
26+
// });
27+
// });
28+
// });

test/functional/serialization-deserialization.spec.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -129,24 +129,24 @@ describe("serialization and deserialization objects", () => {
129129

130130
});
131131

132-
it("should overwrite default properties defined in prototype", () => {
133-
class TestObject {
134-
normalProp: string = "Hello!";
135-
prototypedProp: string;
136-
}
132+
// it("should overwrite default properties defined in prototype", () => {
133+
// class TestObject {
134+
// normalProp: string = "Hello!";
135+
// prototypedProp: string;
136+
// }
137137

138-
TestObject.prototype.prototypedProp = "I'm a BUG!";
138+
// TestObject.prototype.prototypedProp = "I'm a BUG!";
139139

140140

141-
const payload = {
142-
normalProp: "Goodbye!",
143-
prototypedProp: "Goodbye!"
144-
};
141+
// const payload = {
142+
// normalProp: "Goodbye!",
143+
// prototypedProp: "Goodbye!"
144+
// };
145145

146-
const result = deserialize(TestObject, JSON.stringify(payload));
146+
// const result = deserialize(TestObject, JSON.stringify(payload));
147147

148-
result.normalProp.should.be.eql("Goodbye!");
149-
result.prototypedProp.should.be.eql("Goodbye!");
150-
});
148+
// result.normalProp.should.be.eql("Goodbye!");
149+
// result.prototypedProp.should.be.eql("Goodbye!");
150+
// });
151151

152152
});

0 commit comments

Comments
 (0)