UNPKG

24.5 kBMarkdownView Raw
1# class-transformer
2
3[![Build Status](https://travis-ci.org/typestack/class-transformer.svg?branch=master)](https://travis-ci.org/typestack/class-transformer)
4[![codecov](https://codecov.io/gh/typestack/class-transformer/branch/master/graph/badge.svg)](https://codecov.io/gh/typestack/class-transformer)
5[![npm version](https://badge.fury.io/js/class-transformer.svg)](https://badge.fury.io/js/class-transformer)
6[![Dependency Status](https://david-dm.org/typestack/class-transformer.svg)](https://david-dm.org/typestack/class-transformer)
7[![Join the chat at https://gitter.im/typestack/class-transformer](https://badges.gitter.im/typestack/class-transformer.svg)](https://gitter.im/typestack/class-transformer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
8
9Its ES6 and Typescript era. Nowadays you are working with classes and constructor objects more then ever.
10Class-transformer allows you to transform plain object to some instance of class and versa.
11Also it allows to serialize / deserialize object based on criteria.
12This tool is super useful on both frontend and backend.
13
14Example how to use with angular 2 in [plunker](http://plnkr.co/edit/Mja1ZYAjVySWASMHVB9R).
15Source code is available [here](https://github.com/pleerock/class-transformer-demo).
16
17## What is class-transformer
18
19In JavaScript there are two types of objects:
20
21* plain (literal) objects
22* class (constructor) objects
23
24Plain objects are objects that are instances of `Object` class.
25Sometimes they are called **literal** objects, when created via `{}` notation.
26Class objects are instances of classes with own defined constructor, properties and methods.
27Usually you define them via `class` notation.
28
29So, what is the problem?
30
31Sometimes you want to transform plain javascript object to the ES6 **classes** you have.
32For example, if you are loading a json from your backend, some api or from a json file,
33and after you `JSON.parse` it you have a plain javascript object, not instance of class you have.
34
35For example you have a list of users in your `users.json` that you are loading:
36
37```json
38[{
39 "id": 1,
40 "firstName": "Johny",
41 "lastName": "Cage",
42 "age": 27
43},
44{
45 "id": 2,
46 "firstName": "Ismoil",
47 "lastName": "Somoni",
48 "age": 50
49},
50{
51 "id": 3,
52 "firstName": "Luke",
53 "lastName": "Dacascos",
54 "age": 12
55}]
56```
57And you have a `User` class:
58
59```javascript
60export class User {
61 id: number;
62 firstName: string;
63 lastName: string;
64 age: number;
65
66 getName() {
67 return this.firstName + " " + this.lastName;
68 }
69
70 isAdult() {
71 return this.age > 36 && this.age < 60;
72 }
73}
74```
75
76You are assuming that you are downloading users of type `User` from `users.json` file and may want to write
77following code:
78
79```javascript
80fetch("users.json").then((users: User[]) => {
81 // you can use users here, and type hinting also will be available to you,
82 // but users are not actually instances of User class
83 // this means that you can't use methods of User class
84});
85```
86
87In this code you can use `users[0].id`, you can also use `users[0].firstName` and `users[0].lastName`.
88However you cannot use `users[0].getName()` or `users[0].isAdult()` because "users" actually is
89array of plain javascript objects, not instances of User object.
90You actually lied to compiler when you said that its `users: User[]`.
91
92So what to do? How to make a `users` array of instances of `User` objects instead of plain javascript objects?
93Solution is to create new instances of User object and manually copy all properties to new objects.
94But things may go wrong very fast once you have a more complex object hierarchy.
95
96Alternatives? Yes, you can use class-transformer. Purpose of this library is to help you to map you plain javascript
97objects to the instances of classes you have.
98
99This library also great for models exposed in your APIs,
100because it provides a great tooling to control what your models are exposing in your API.
101Here is example how it will look like:
102
103```javascript
104fetch("users.json").then((users: Object[]) => {
105 const realUsers = plainToClass(User, users);
106 // now each user in realUsers is instance of User class
107});
108```
109
110Now you can use `users[0].getName()` and `users[0].isAdult()` methods.
111
112## Installation
113
114### Node.js
115
1161. Install module:
117
118 `npm install class-transformer --save`
119
1202. `reflect-metadata` shim is required, install it too:
121
122 `npm install reflect-metadata --save`
123
124 and make sure to import it in a global place, like app.ts:
125
126 ```javascript
127 import "reflect-metadata";
128 ```
129
1303. ES6 features are used, if you are using old version of node.js you may need to install es6-shim:
131
132 `npm install es6-shim --save`
133
134 and import it in a global place like app.ts:
135
136 ```javascript
137 import "es6-shim";
138 ```
139
140### Browser
141
1421. Install module:
143
144 `npm install class-transformer --save`
145
1462. `reflect-metadata` shim is required, install it too:
147
148 `npm install reflect-metadata --save`
149
150 add `<script>` to reflect-metadata in the head of your `index.html`:
151
152 ```html
153 <html>
154 <head>
155 <!-- ... -->
156 <script src="node_modules/reflect-metadata/Reflect.js"></script>
157 </head>
158 <!-- ... -->
159 </html>
160 ```
161
162 If you are using angular 2 you should already have this shim installed.
163
1643. If you are using system.js you may want to add this into `map` and `package` config:
165
166 ```json
167 {
168 "map": {
169 "class-transformer": "node_modules/class-transformer"
170 },
171 "packages": {
172 "class-transformer": { "main": "index.js", "defaultExtension": "js" }
173 }
174 }
175 ```
176
177## Methods
178
179#### plainToClass
180
181This method transforms a plain javascript object to instance of specific class.
182
183```javascript
184import {plainToClass} from "class-transformer";
185
186let users = plainToClass(User, userJson); // to convert user plain object a single user. also supports arrays
187```
188
189#### plainToClassFromExist
190
191This method transforms a plain object into a instance using a already filled Object which is a instance from the target class.
192
193```javascript
194const defaultUser = new User();
195defaultUser.role = 'user';
196
197let mixedUser = plainToClassFromExist(defaultUser, user); // mixed user should have the value role = user when no value is set otherwise.
198```
199
200#### classToPlain
201
202This method transforms your class object back to plain javascript object, that can be `JSON.stringify` later.
203
204```javascript
205import {classToPlain} from "class-transformer";
206let photo = classToPlain(photo);
207```
208
209#### classToClass
210
211This method transforms your class object into new instance of the class object.
212This maybe treated as deep clone of your objects.
213
214```javascript
215import {classToClass} from "class-transformer";
216let photo = classToClass(photo);
217```
218
219You can also use a `ignoreDecorators` option in transformation options to ignore all decorators you classes is using.
220
221#### serialize
222
223You can serialize your model right to the json using `serialize` method:
224
225```javascript
226import {serialize} from "class-transformer";
227let photo = serialize(photo);
228```
229
230`serialize` works with both arrays and non-arrays.
231
232#### deserialize and deserializeArray
233
234You can deserialize your model to from a json using `deserialize` method:
235
236```javascript
237import {deserialize} from "class-transformer";
238let photo = deserialize(Photo, photo);
239```
240
241To make deserialization to work with arrays use `deserializeArray` method:
242
243```javascript
244import {deserializeArray} from "class-transformer";
245let photos = deserializeArray(Photo, photos);
246```
247
248## Enforcing type-safe instance
249
250The default behaviour of the `plainToClass` method is to set *all* properties from the plain object,
251even those which are not specified in the class.
252
253```javascript
254import {plainToClass} from "class-transformer";
255
256class User {
257 id: number
258 firstName: string
259 lastName: string
260}
261
262const fromPlainUser = {
263 unkownProp: 'hello there',
264 firstName: 'Umed',
265 lastName: 'Khudoiberdiev',
266}
267
268console.log(plainToClass(User, fromPlainUser))
269
270// User {
271// unkownProp: 'hello there',
272// firstName: 'Umed',
273// lastName: 'Khudoiberdiev',
274// }
275```
276
277If this behaviour does not suit your needs, you can use the `excludeExtraneousValues` option
278in the `plainToClass` method while *exposing all your class properties* as a requirement.
279
280```javascript
281import {Expose, plainToClass} from "class-transformer";
282
283class User {
284 @Expose() id: number;
285 @Expose() firstName: string;
286 @Expose() lastName: string;
287}
288
289const fromPlainUser = {
290 unkownProp: 'hello there',
291 firstName: 'Umed',
292 lastName: 'Khudoiberdiev',
293}
294
295console.log(plainToClass(User, fromPlainUser, { excludeExtraneousValues: true }))
296
297// User {
298// id: undefined,
299// firstName: 'Umed',
300// lastName: 'Khudoiberdiev'
301// }
302```
303
304## Working with nested objects
305
306When you are trying to transform objects that have nested objects,
307its required to known what type of object you are trying to transform.
308Since Typescript does not have good reflection abilities yet,
309we should implicitly specify what type of object each property contain.
310This is done using `@Type` decorator.
311
312Lets say we have an album with photos.
313And we are trying to convert album plain object to class object:
314
315```javascript
316import {Type, plainToClass} from "class-transformer";
317
318export class Album {
319
320 id: number;
321
322 name: string;
323
324 @Type(() => Photo)
325 photos: Photo[];
326}
327
328export class Photo {
329 id: number;
330 filename: string;
331}
332
333let album = plainToClass(Album, albumJson);
334// now album is Album object with Photo objects inside
335```
336
337### Providing more than one type option
338
339In case the nested object can be of different types, you can provide an additional options object,
340that specifies a discriminator. The discriminator option must define a `property` that holds the sub
341type name for the object and the possible `subTypes`, the nested object can converted to. A sub type
342has a `value`, that holds the constructor of the Type and the `name`, that can match with the `property`
343of the discriminator.
344
345Lets say we have an album that has a top photo. But this photo can be of certain different types.
346And we are trying to convert album plain object to class object. The plain object input has to define
347the additional property `__type`. This property is removed during transformation by default:
348
349**JSON input**:
350```json
351{
352 "id": 1,
353 "name": "foo",
354 "topPhoto": {
355 "id": 9,
356 "filename": "cool_wale.jpg",
357 "depth": 1245,
358 "__type": "underwater"
359 }
360}
361```
362
363```javascript
364import {Type, plainToClass} from "class-transformer";
365
366export abstract class Photo {
367 id: number;
368 filename: string;
369}
370
371export class Landscape extends Photo {
372 panorama: boolean;
373}
374
375export class Portrait extends Photo {
376 person: Person;
377}
378
379export class UnderWater extends Photo {
380 depth: number;
381}
382
383export class Album {
384
385 id: number;
386 name: string;
387
388 @Type(() => Photo, {
389 discriminator: {
390 property: "__type",
391 subTypes: [
392 { value: Landscape, name: "landscape" },
393 { value: Portrait, name: "portrait" },
394 { value: UnderWater, name: "underwater" }
395 ]
396 }
397 })
398 topPhoto: Landscape | Portrait | UnderWater;
399
400}
401
402let album = plainToClass(Album, albumJson);
403// now album is Album object with a UnderWater object without `__type` property.
404```
405
406Hint: The same applies for arrays with different sub types. Moreover you can specify `keepDiscriminatorProperty: true`
407in the options to keep the discriminator property also inside your resulting class.
408
409## Exposing getters and method return values
410
411You can expose what your getter or method return by setting a `@Expose()` decorator to those getters or methods:
412
413```javascript
414import {Expose} from "class-transformer";
415
416export class User {
417
418 id: number;
419 firstName: string;
420 lastName: string;
421 password: string;
422
423 @Expose()
424 get name() {
425 return this.firstName + " " + this.lastName;
426 }
427
428 @Expose()
429 getFullName() {
430 return this.firstName + " " + this.lastName;
431 }
432}
433```
434
435## Exposing properties with different names
436
437If you want to expose some of properties with a different name,
438you can do it by specifying a `name` option to `@Expose` decorator:
439
440```javascript
441import {Expose} from "class-transformer";
442
443export class User {
444
445 @Expose({ name: "uid" })
446 id: number;
447
448 firstName: string;
449
450 lastName: string;
451
452 @Expose({ name: "secretKey" })
453 password: string;
454
455 @Expose({ name: "fullName" })
456 getFullName() {
457 return this.firstName + " " + this.lastName;
458 }
459}
460```
461
462## Skipping specific properties
463
464Sometimes you want to skip some properties during transformation.
465This can be done using `@Exclude` decorator:
466
467```javascript
468import {Exclude} from "class-transformer";
469
470export class User {
471
472 id: number;
473
474 email: string;
475
476 @Exclude()
477 password: string;
478}
479```
480
481Now when you transform a User, `password` property will be skipped and not be included in the transformed result.
482
483## Skipping depend of operation
484
485You can control on what operation you will exclude a property. Use `toClassOnly` or `toPlainOnly` options:
486
487```javascript
488import {Exclude} from "class-transformer";
489
490export class User {
491
492 id: number;
493
494 email: string;
495
496 @Exclude({ toPlainOnly: true })
497 password: string;
498}
499```
500
501Now `password` property will be excluded only during `classToPlain` operation. Oppositely, use `toClassOnly` option.
502
503## Skipping all properties of the class
504
505You can skip all properties of the class, and expose only those are needed explicitly:
506
507```javascript
508import {Exclude, Expose} from "class-transformer";
509
510@Exclude()
511export class User {
512
513 @Expose()
514 id: number;
515
516 @Expose()
517 email: string;
518
519 password: string;
520}
521```
522
523Now `id` and `email` will be exposed, and password will be excluded during transformation.
524Alternatively, you can set exclusion strategy during transformation:
525
526```javascript
527import {classToPlain} from "class-transformer";
528let photo = classToPlain(photo, { strategy: "excludeAll" });
529```
530
531In this case you don't need to `@Exclude()` a whole class.
532
533## Skipping private properties, or some prefixed properties
534
535If you name your private properties with a prefix, lets say with `_`,
536then you can exclude such properties from transformation too:
537
538```javascript
539import {classToPlain} from "class-transformer";
540let photo = classToPlain(photo, { excludePrefixes: ["_"] });
541```
542
543This will skip all properties that start with `_` prefix.
544You can pass any number of prefixes and all properties that begin with these prefixes will be ignored.
545For example:
546
547```javascript
548import {Expose} from "class-transformer";
549
550export class User {
551
552 id: number;
553 private _firstName: string;
554 private _lastName: string;
555 _password: string;
556
557 setName(firstName: string, lastName: string) {
558 this._firstName = firstName;
559 this._lastName = lastName;
560 }
561
562 @Expose()
563 get name() {
564 return this.firstName + " " + this.lastName;
565 }
566
567}
568
569const user = new User();
570user.id = 1;
571user.setName("Johny", "Cage");
572user._password = 123;
573
574const plainUser = classToPlain(user, { excludePrefixes: ["_"] });
575// here plainUser will be equal to
576// { id: 1, name: "Johny Cage" }
577```
578
579## Using groups to control excluded properties
580
581You can use groups to control what data will be exposed and what will not be:
582
583```javascript
584import {Exclude, Expose} from "class-transformer";
585
586@Exclude()
587export class User {
588
589 id: number;
590
591 name: string;
592
593 @Expose({ groups: ["user", "admin"] }) // this means that this data will be exposed only to users and admins
594 email: string;
595
596 @Expose({ groups: ["user"] }) // this means that this data will be exposed only to users
597 password: string;
598}
599```
600
601```javascript
602import {classToPlain} from "class-transformer";
603let user1 = classToPlain(user, { groups: ["user"] }); // will contain id, name, email and password
604let user2 = classToPlain(user, { groups: ["admin"] }); // will contain id, name and email
605```
606
607## Using versioning to control exposed and excluded properties
608
609If you are building an API that has different versions, class-transformer has extremely useful tools for that.
610You can control which properties of your model should be exposed or excluded in what version. Example:
611
612```javascript
613import {Exclude, Expose} from "class-transformer";
614
615@Exclude()
616export class User {
617
618 id: number;
619
620 name: string;
621
622 @Expose({ since: 0.7, until: 1 }) // this means that this property will be exposed for version starting from 0.7 until 1
623 email: string;
624
625 @Expose({ since: 2.1 }) // this means that this property will be exposed for version starting from 2.1
626 password: string;
627}
628```
629
630```javascript
631import {classToPlain} from "class-transformer";
632let user1 = classToPlain(user, { version: 0.5 }); // will contain id and name
633let user2 = classToPlain(user, { version: 0.7 }); // will contain id, name and email
634let user3 = classToPlain(user, { version: 1 }); // will contain id and name
635let user4 = classToPlain(user, { version: 2 }); // will contain id and name
636let user5 = classToPlain(user, { version: 2.1 }); // will contain id, name nad password
637```
638
639## Сonverting date strings into Date objects
640
641Sometimes you have a Date in your plain javascript object received in a string format.
642And you want to create a real javascript Date object from it.
643You can do it simply by passing a Date object to the `@Type` decorator:
644
645```javascript
646import {Type} from "class-transformer";
647
648export class User {
649
650 id: number;
651
652 email: string;
653
654 password: string;
655
656 @Type(() => Date)
657 registrationDate: Date;
658}
659```
660
661Note, that dates will be converted to strings when you'll try to convert class object to plain object.
662
663Same technique can be used with `Number`, `String`, `Boolean`
664primitive types when you want to convert your values into these types.
665
666## Working with arrays
667
668When you are using arrays you must provide a type of the object that array contains.
669This type, you specify in a `@Type()` decorator:
670
671```javascript
672import {Type} from "class-transformer";
673
674export class Photo {
675
676 id: number;
677
678 name: string;
679
680 @Type(() => Album)
681 albums: Album[];
682}
683```
684
685You can also use custom array types:
686
687```javascript
688import {Type} from "class-transformer";
689
690export class AlbumCollection extends Array<Album> {
691 // custom array functions ...
692}
693
694export class Photo {
695
696 id: number;
697
698 name: string;
699
700 @Type(() => Album)
701 albums: AlbumCollection;
702}
703```
704
705Library will handle proper transformation automatically.
706
707ES6 collections `Set` and `Map` also require the `@Type` decorator:
708
709```javascript
710export class Skill {
711 name: string;
712}
713
714export class Weapon {
715 name: string;
716 range: number;
717}
718
719export class Player {
720 name: string;
721
722 @Type(() => Skill)
723 skills: Set<Skill>;
724
725 @Type(() => Weapon)
726 weapons: Map<string, Weapon>;
727}
728```
729
730## Additional data transformation
731
732### Basic usage
733
734You can perform additional data transformation using `@Transform` decorator.
735For example, you want to make your `Date` object to be a `moment` object when you are
736transforming object from plain to class:
737
738```javascript
739import {Transform} from "class-transformer";
740import * as moment from "moment";
741import {Moment} from "moment";
742
743export class Photo {
744
745 id: number;
746
747 @Type(() => Date)
748 @Transform(value => moment(value), { toClassOnly: true })
749 date: Moment;
750}
751```
752
753Now when you call `plainToClass` and send a plain representation of the Photo object,
754it will convert a date value in your photo object to moment date.
755`@Transform` decorator also supports groups and versioning.
756
757### Advanced usage
758
759The `@Transform` decorator is given more arguments to let you configure how you want the transformation to be done.
760
761```
762@Transform((value, obj, type) => value)
763```
764
765| Argument | Description
766|--------------------|---------------------------------------------------------------------------------|
767| `value` | The property value before the transformation.
768| `obj` | The transformation source object.
769| `type` | The transformation type.
770
771## Other decorators
772| Signature | Example | Description
773|--------------------|------------------------------------------|---------------------------------------------|
774| `@TransformClassToPlain` | `@TransformClassToPlain({ groups: ["user"] })` | Transform the method return with classToPlain and expose the properties on the class.
775| `@TransformClassToClass` | `@TransformClassToClass({ groups: ["user"] })` | Transform the method return with classToClass and expose the properties on the class.
776| `@TransformPlainToClass` | `@TransformPlainToClass(User, { groups: ["user"] })` | Transform the method return with plainToClass and expose the properties on the class.
777
778The above decorators accept one optional argument:
779ClassTransformOptions - The transform options like groups, version, name
780
781An example:
782
783```javascript
784@Exclude()
785class User {
786
787 id: number;
788
789 @Expose()
790 firstName: string;
791
792 @Expose()
793 lastName: string;
794
795 @Expose({ groups: ['user.email'] })
796 email: string;
797
798 password: string;
799}
800
801class UserController {
802
803 @TransformClassToPlain({ groups: ['user.email'] })
804 getUser() {
805 const user = new User();
806 user.firstName = "Snir";
807 user.lastName = "Segal";
808 user.password = "imnosuperman";
809
810 return user;
811 }
812}
813
814const controller = new UserController();
815const user = controller.getUser();
816```
817
818the `user` variable will contain only firstName,lastName, email properties becuase they are
819the exposed variables. email property is also exposed becuase we metioned the group "user.email".
820
821## Working with generics
822
823Generics are not supported because TypeScript does not have good reflection abilities yet.
824Once TypeScript team provide us better runtime type reflection tools, generics will be implemented.
825There are some tweaks however you can use, that maybe can solve your problem.
826[Checkout this example.](https://github.com/pleerock/class-transformer/tree/master/sample/sample4-generics)
827
828## How does it handle circular references?
829
830Circular references are ignored.
831For example, if you are transforming class `User` that contains property `photos` with type of `Photo`,
832 and `Photo` contains link `user` to its parent `User`, then `user` will be ignored during transformation.
833Circular references are not ignored only during `classToClass` operation.
834
835## Example with Angular2
836
837Lets say you want to download users and want them automatically to be mapped to the instances of `User` class.
838
839```javascript
840import {plainToClass} from "class-transformer";
841
842this.http
843 .get("users.json")
844 .map(res => res.json())
845 .map(res => plainToClass(User, res as Object[]))
846 .subscribe(users => {
847 // now "users" is type of User[] and each user has getName() and isAdult() methods available
848 console.log(users);
849 });
850```
851
852You can also inject a class `ClassTransformer` as a service in `providers`, and use its methods.
853
854Example how to use with angular 2 in [plunker](http://plnkr.co/edit/Mja1ZYAjVySWASMHVB9R).
855Source code is [here](https://github.com/pleerock/class-transformer-demo).
856
857## Samples
858
859Take a look on samples in [./sample](https://github.com/pleerock/class-transformer/tree/master/sample) for more examples of
860usages.
861
862## Release notes
863
864See information about breaking changes and release notes [here](https://github.com/pleerock/class-transformer/tree/master/doc/release-notes.md).