1 | # class-transformer
|
2 |
|
3 | [![Build Status](https://travis-ci.org/pleerock/class-transformer.svg?branch=master)](https://travis-ci.org/pleerock/class-transformer)
|
4 | [![codecov](https://codecov.io/gh/pleerock/class-transformer/branch/master/graph/badge.svg)](https://codecov.io/gh/pleerock/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/pleerock/class-transformer.svg)](https://david-dm.org/pleerock/class-transformer)
|
7 | [![devDependency Status](https://david-dm.org/pleerock/class-transformer/dev-status.svg)](https://david-dm.org/pleerock/class-transformer#info=devDependencies)
|
8 | [![Join the chat at https://gitter.im/pleerock/class-transformer](https://badges.gitter.im/pleerock/class-transformer.svg)](https://gitter.im/pleerock/class-transformer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
9 |
|
10 | Its ES6 and Typescript era. Nowadays you are working with classes and constructor objects more then ever.
|
11 | Class-transformer allows you to transform plain object to some instance of class and versa.
|
12 | Also it allows to serialize / deserialize object based on criteria.
|
13 | This tool is super useful on both frontend and backend.
|
14 |
|
15 | Example how to use with angular 2 in [plunker](http://plnkr.co/edit/Mja1ZYAjVySWASMHVB9R).
|
16 | Source code is available [here](https://github.com/pleerock/class-transformer-demo).
|
17 |
|
18 | ## What is class-transformer
|
19 |
|
20 | In JavaScript there are two types of objects:
|
21 |
|
22 | * plain (literal) objects
|
23 | * class (constructor) objects
|
24 |
|
25 | Plain objects are objects that are instances of `Object` class.
|
26 | Sometimes they are called **literal** objects, when created via `{}` notation.
|
27 | Class objects are instances of classes with own defined constructor, properties and methods.
|
28 | Usually you define them via `class` notation.
|
29 |
|
30 | So, what is the problem?
|
31 |
|
32 | Sometimes you want to transform plain javascript object to the ES6 **classes** you have.
|
33 | For example, if you are loading a json from your backend, some api or from a json file,
|
34 | and after you `JSON.parse` it you have a plain javascript object, not instance of class you have.
|
35 |
|
36 | For example you have a list of users in your `users.json` that you are loading:
|
37 |
|
38 | ```json
|
39 | [{
|
40 | "id": 1,
|
41 | "firstName": "Johny",
|
42 | "lastName": "Cage",
|
43 | "age": 27
|
44 | },
|
45 | {
|
46 | "id": 2,
|
47 | "firstName": "Ismoil",
|
48 | "lastName": "Somoni",
|
49 | "age": 50
|
50 | },
|
51 | {
|
52 | "id": 3,
|
53 | "firstName": "Luke",
|
54 | "lastName": "Dacascos",
|
55 | "age": 12
|
56 | }]
|
57 | ```
|
58 | And you have a `User` class:
|
59 |
|
60 | ```javascript
|
61 | export class User {
|
62 | id: number;
|
63 | firstName: string;
|
64 | lastName: string;
|
65 | age: number;
|
66 |
|
67 | getName() {
|
68 | return this.firstName + " " + this.lastName;
|
69 | }
|
70 |
|
71 | isAdult() {
|
72 | return this.age > 36 && this.age < 60;
|
73 | }
|
74 | }
|
75 | ```
|
76 |
|
77 | You are assuming that you are downloading users of type `User` from `users.json` file and may want to write
|
78 | following code:
|
79 |
|
80 | ```javascript
|
81 | fetch("users.json").then((users: User[]) => {
|
82 | // you can use users here, and type hinting also will be available to you,
|
83 | // but users are not actually instances of User class
|
84 | // this means that you can't use methods of User class
|
85 | });
|
86 | ```
|
87 |
|
88 | In this code you can use `users[0].id`, you can also use `users[0].firstName` and `users[0].lastName`.
|
89 | However you cannot use `users[0].getName()` or `users[0].isAdult()` because "users" actually is
|
90 | array of plain javascript objects, not instances of User object.
|
91 | You actually lied to compiler when you said that its `users: User[]`.
|
92 |
|
93 | So what to do? How to make a `users` array of instances of `User` objects instead of plain javascript objects?
|
94 | Solution is to create new instances of User object and manually copy all properties to new objects.
|
95 | But things may go wrong very fast once you have a more complex object hierarchy.
|
96 |
|
97 | Alternatives? Yes, you can use class-transformer. Purpose of this library is to help you to map you plain javascript
|
98 | objects to the instances of classes you have.
|
99 |
|
100 | This library also great for models exposed in your APIs,
|
101 | because it provides a great tooling to control what your models are exposing in your API.
|
102 | Here is example how it will look like:
|
103 |
|
104 | ```javascript
|
105 | fetch("users.json").then((users: Object[]) => {
|
106 | const realUsers = plainToClass(users);
|
107 | // now each user in realUsers is instance of User class
|
108 | });
|
109 | ```
|
110 |
|
111 | Now you can use `users[0].getName()` and `users[0].isAdult()` methods.
|
112 |
|
113 | ## Installation
|
114 |
|
115 | ### Node.js
|
116 |
|
117 | 1. Install module:
|
118 |
|
119 | `npm install class-transformer --save`
|
120 |
|
121 | 2. `reflect-metadata` shim is required, install it too:
|
122 |
|
123 | `npm install reflect-metadata --save`
|
124 |
|
125 | and make sure to import it in a global place, like app.ts:
|
126 |
|
127 | ```javascript
|
128 | import "reflect-metadata";
|
129 | ```
|
130 |
|
131 | 3. ES6 features are used, if you are using old version of node.js you may need to install es6-shim:
|
132 |
|
133 | `npm install es6-shim --save`
|
134 |
|
135 | and import it in a global place like app.ts:
|
136 |
|
137 | ```javascript
|
138 | import "es6-shim";
|
139 | ```
|
140 |
|
141 | ### Browser
|
142 |
|
143 | 1. Install module:
|
144 |
|
145 | `npm install class-transformer --save`
|
146 |
|
147 | 2. `reflect-metadata` shim is required, install it too:
|
148 |
|
149 | `npm install reflect-metadata --save`
|
150 |
|
151 | add `<script>` to reflect-metadata in the head of your `index.html`:
|
152 |
|
153 | ```html
|
154 | <html>
|
155 | <head>
|
156 | <!-- ... -->
|
157 | <script src="node_modules/reflect-metadata/Reflect.js"></script>
|
158 | </head>
|
159 | <!-- ... -->
|
160 | </html>
|
161 | ```
|
162 |
|
163 | If you are using angular 2 you should already have this shim installed.
|
164 |
|
165 | 3. If you are using system.js you may want to add this into `map` and `package` config:
|
166 |
|
167 | ```json
|
168 | {
|
169 | "map": {
|
170 | "class-transformer": "node_modules/class-transformer"
|
171 | },
|
172 | "packages": {
|
173 | "class-transformer": { "main": "index.js", "defaultExtension": "js" }
|
174 | }
|
175 | }
|
176 | ```
|
177 |
|
178 | ## Methods
|
179 |
|
180 | #### plainToClass
|
181 |
|
182 | This method transforms a plain javascript object to instance of specific class.
|
183 |
|
184 | ```javascript
|
185 | import {plainToClass} from "class-transformer";
|
186 |
|
187 | let users = plainToClass(User, userJson); // to convert user plain object a single user. also supports arrays
|
188 | ```
|
189 |
|
190 | #### classToPlain
|
191 |
|
192 | This method transforms your class object back to plain javascript object, that can be `JSON.stringify` later.
|
193 |
|
194 | ```javascript
|
195 | import {classToPlain} from "class-transformer";
|
196 | let photo = classToPlain(photo);
|
197 | ```
|
198 |
|
199 | #### classToClass
|
200 |
|
201 | This method transforms your class object into new instance of the class object.
|
202 | This maybe treated as deep clone of your objects.
|
203 |
|
204 | ```javascript
|
205 | import {classToClass} from "class-transformer";
|
206 | let photo = classToClass(photo);
|
207 | ```
|
208 |
|
209 | You can also use a `ignoreDecorators` option in transformation options to ignore all decorators you classes is using.
|
210 |
|
211 | #### serialize
|
212 |
|
213 | You can serialize your model right to the json using `serialize` method:
|
214 |
|
215 | ```javascript
|
216 | import {serialize} from "class-transformer";
|
217 | let photo = serialize(photo);
|
218 | ```
|
219 |
|
220 | `serialize` works with both arrays and non-arrays.
|
221 |
|
222 | #### deserialize and deserializeArray
|
223 |
|
224 | You can deserialize your model to from a json using `deserialize` method:
|
225 |
|
226 | ```javascript
|
227 | import {deserialize} from "class-transformer";
|
228 | let photo = deserialize(photo);
|
229 | ```
|
230 |
|
231 | To make deserialization to work with arrays use `deserializeArray` method:
|
232 |
|
233 | ```javascript
|
234 | import {deserializeArray} from "class-transformer";
|
235 | let photos = deserializeArray(photos);
|
236 | ```
|
237 |
|
238 | ## Working with nested objects
|
239 |
|
240 | When you are trying to transform objects that have nested objects,
|
241 | its required to known what type of object you are trying to transform.
|
242 | Since Typescript does not have good reflection abilities yet,
|
243 | we should implicitly specify what type of object each property contain.
|
244 | This is done using `@Type` decorator.
|
245 |
|
246 | Lets say we have an album with photos.
|
247 | And we are trying to convert album plain object to class object:
|
248 |
|
249 | ```javascript
|
250 | import {Type, plainToClass} from "class-transformer";
|
251 |
|
252 | export class Album {
|
253 |
|
254 | id: number;
|
255 |
|
256 | name: string;
|
257 |
|
258 | @Type(() => Photo)
|
259 | photos: Photo[];
|
260 | }
|
261 |
|
262 | export class Photo {
|
263 | id: number;
|
264 | filename: string;
|
265 | }
|
266 |
|
267 | let album = plainToClass(Album, albumJson);
|
268 | // now album is Album object with Photo objects inside
|
269 | ```
|
270 |
|
271 | ## Exposing getters and method return values
|
272 |
|
273 | You can expose what your getter or method return by setting a `@Expose()` decorator to those getters or methods:
|
274 |
|
275 | ```javascript
|
276 | import {Expose} from "class-transformer";
|
277 |
|
278 | export class User {
|
279 |
|
280 | id: number;
|
281 | firstName: string;
|
282 | lastName: string;
|
283 | password: string;
|
284 |
|
285 | @Expose()
|
286 | get name() {
|
287 | return this.firstName + " " + this.lastName;
|
288 | }
|
289 |
|
290 | @Expose()
|
291 | getFullName() {
|
292 | return this.firstName + " " + this.lastName;
|
293 | }
|
294 | }
|
295 | ```
|
296 |
|
297 | ## Exposing properties with different names
|
298 |
|
299 | If you want to expose some of properties with a different name,
|
300 | you can do it by specifying a `name` option to `@Expose` decorator:
|
301 |
|
302 | ```javascript
|
303 | import {Expose} from "class-transformer";
|
304 |
|
305 | export class User {
|
306 |
|
307 | @Expose("uid")
|
308 | id: number;
|
309 |
|
310 | firstName: string;
|
311 |
|
312 | lastName: string;
|
313 |
|
314 | @Expose("secretKey")
|
315 | password: string;
|
316 |
|
317 | @Expose("fullName")
|
318 | getFullName() {
|
319 | return this.firstName + " " + this.lastName;
|
320 | }
|
321 | }
|
322 | ```
|
323 |
|
324 | ## Skipping specific properties
|
325 |
|
326 | Sometimes you want to skip some properties during transformation.
|
327 | This can be done using `@Exclude` decorator:
|
328 |
|
329 | ```javascript
|
330 | import {Exclude} from "class-transformer";
|
331 |
|
332 | export class User {
|
333 |
|
334 | id: number;
|
335 |
|
336 | email: string;
|
337 |
|
338 | @Exclude()
|
339 | password: string;
|
340 | }
|
341 | ```
|
342 |
|
343 | Now when you transform a User, `password` property will be skipped and not be included in the transformed result.
|
344 |
|
345 | ## Skipping depend of operation
|
346 |
|
347 | You can control on what operation you will exclude a property. Use `toClassOnly` or `toPlainOnly` options:
|
348 |
|
349 | ```javascript
|
350 | import {Exclude} from "class-transformer";
|
351 |
|
352 | export class User {
|
353 |
|
354 | id: number;
|
355 |
|
356 | email: string;
|
357 |
|
358 | @Exclude({ toPlainOnly: true })
|
359 | password: string;
|
360 | }
|
361 | ```
|
362 |
|
363 | Now `password` property will be excluded only during `classToPlain` operation. Oppositely, use `toClassOnly` option.
|
364 |
|
365 | ## Skipping all properties of the class
|
366 |
|
367 | You can skip all properties of the class, and expose only those are needed explicitly:
|
368 |
|
369 | ```javascript
|
370 | import {Exclude, Expose} from "class-transformer";
|
371 |
|
372 | @Exclude()
|
373 | export class User {
|
374 |
|
375 | @Expose()
|
376 | id: number;
|
377 |
|
378 | @Expose()
|
379 | email: string;
|
380 |
|
381 | password: string;
|
382 | }
|
383 | ```
|
384 |
|
385 | Now `id` and `email` will be exposed, and password will be excluded during transformation.
|
386 | Alternatively, you can set exclusion strategy during transformation:
|
387 |
|
388 | ```javascript
|
389 | import {classToPlain} from "class-transformer";
|
390 | let photo = classToPlain(photo, { strategy: "excludeAll" });
|
391 | ```
|
392 |
|
393 | In this case you don't need to `@Exclude()` a whole class.
|
394 |
|
395 | ## Skipping private properties, or some prefixed properties
|
396 |
|
397 | If you name your private properties with a prefix, lets say with `_`,
|
398 | then you can exclude such properties from transformation too:
|
399 |
|
400 | ```javascript
|
401 | import {classToPlain} from "class-transformer";
|
402 | let photo = classToPlain(photo, { excludePrefixes: ["_"] });
|
403 | ```
|
404 |
|
405 | This will skip all properties that start with `_` prefix.
|
406 | You can pass any number of prefixes and all properties that begin with these prefixes will be ignored.
|
407 | For example:
|
408 |
|
409 | ```javascript
|
410 | import {Expose} from "class-transformer";
|
411 |
|
412 | export class User {
|
413 |
|
414 | id: number;
|
415 | private _firstName: string;
|
416 | private _lastName: string;
|
417 | _password: string;
|
418 |
|
419 | setName(firstName: string, lastName: string) {
|
420 | this._firstName = firstName;
|
421 | this._lastName = lastName;
|
422 | }
|
423 |
|
424 | @Expose()
|
425 | get name() {
|
426 | return this.firstName + " " + this.lastName;
|
427 | }
|
428 |
|
429 | }
|
430 |
|
431 | const user = new User();
|
432 | user.id = 1;
|
433 | user.setName("Johny", "Cage");
|
434 | user._password = 123;
|
435 |
|
436 | const plainUser = classToPlain(user, { excludePrefixes: ["_"] });
|
437 | // here plainUser will be equal to
|
438 | // { id: 1, name: "Johny Cage" }
|
439 | ```
|
440 |
|
441 | ## Using groups to control excluded properties
|
442 |
|
443 | You can use groups to control what data will be exposed and what will not be:
|
444 |
|
445 | ```javascript
|
446 | import {Exclude, Expose} from "class-transformer";
|
447 |
|
448 | @Exclude()
|
449 | export class User {
|
450 |
|
451 | id: number;
|
452 |
|
453 | name: string;
|
454 |
|
455 | @Expose({ groups: ["user", "admin"] }) // this means that this data will be exposed only to users and admins
|
456 | email: string;
|
457 |
|
458 | @Expose({ groups: ["user"] }) // this means that this data will be exposed only to users
|
459 | password: string;
|
460 | }
|
461 | ```
|
462 |
|
463 | ```javascript
|
464 | import {classToPlain} from "class-transformer";
|
465 | let user1 = classToPlain(user, { groups: ["user"] }); // will contain id, name, email and password
|
466 | let user2 = classToPlain(user, { groups: ["admin"] }); // will contain id, name and email
|
467 | ```
|
468 |
|
469 | ## Using versioning to control exposed and excluded properties
|
470 |
|
471 | If you are building an API that has different versions, class-transformer has extremely useful tools for that.
|
472 | You can control which properties of your model should be exposed or excluded in what version. Example:
|
473 |
|
474 | ```javascript
|
475 | import {Exclude, Expose} from "class-transformer";
|
476 |
|
477 | @Exclude()
|
478 | export class User {
|
479 |
|
480 | id: number;
|
481 |
|
482 | name: string;
|
483 |
|
484 | @Expose({ since: 0.7, until: 1 }) // this means that this property will be exposed for version starting from 0.7 until 1
|
485 | email: string;
|
486 |
|
487 | @Expose({ since: 2.1 }) // this means that this property will be exposed for version starting from 2.1
|
488 | password: string;
|
489 | }
|
490 | ```
|
491 |
|
492 | ```javascript
|
493 | import {classToPlain} from "class-transformer";
|
494 | let user1 = classToPlain(user, { version: 0.5 }); // will contain id and name
|
495 | let user2 = classToPlain(user, { version: 0.7 }); // will contain id, name and email
|
496 | let user3 = classToPlain(user, { version: 1 }); // will contain id and name
|
497 | let user4 = classToPlain(user, { version: 2 }); // will contain id and name
|
498 | let user5 = classToPlain(user, { version: 2.1 }); // will contain id, name nad password
|
499 | ```
|
500 |
|
501 | ## Сonverting date strings into Date objects
|
502 |
|
503 | Sometimes you have a Date in your plain javascript object received in a string format.
|
504 | And you want to create a real javascript Date object from it.
|
505 | You can do it simply by passing a Date object to the `@Type` decorator:
|
506 |
|
507 | ```javascript
|
508 | import {Type} from "class-transformer";
|
509 |
|
510 | export class User {
|
511 |
|
512 | id: number;
|
513 |
|
514 | email: string;
|
515 |
|
516 | password: string;
|
517 |
|
518 | @Type(() => Date)
|
519 | registrationDate: Date;
|
520 | }
|
521 | ```
|
522 |
|
523 | Note, that dates will be converted to strings when you'll try to convert class object to plain object.
|
524 |
|
525 | Same technique can be used with `Number`, `String`, `Boolean`
|
526 | primitive types when you want to convert your values into these types.
|
527 |
|
528 | ## Working with arrays
|
529 |
|
530 | When you are using arrays you must provide a type of the object that array contains.
|
531 | This type, you specify in a `@Type()` decorator:
|
532 |
|
533 | ```javascript
|
534 | import {ArrayType} from "class-transformer";
|
535 |
|
536 | export class Photo {
|
537 |
|
538 | id: number;
|
539 |
|
540 | name: string;
|
541 |
|
542 | @Type(() => Album)
|
543 | albums: Album[];
|
544 | }
|
545 | ```
|
546 |
|
547 | You can also use custom array types:
|
548 |
|
549 | ```javascript
|
550 | import {ArrayType} from "class-transformer";
|
551 |
|
552 | export class AlbumCollection extends Array<Album> {
|
553 | // custom array functions ...
|
554 | }
|
555 |
|
556 | export class Photo {
|
557 |
|
558 | id: number;
|
559 |
|
560 | name: string;
|
561 |
|
562 | @Type(() => Album)
|
563 | albums: AlbumCollection;
|
564 | }
|
565 | ```
|
566 |
|
567 | Library will handle proper transformation automatically.
|
568 |
|
569 | ## Additional data transformation
|
570 |
|
571 | You can perform additional data transformation using `@Transform` decorator.
|
572 | For example, you want to make your `Date` object to be a `moment` object when you are
|
573 | transforming object from plain to class:
|
574 |
|
575 | ```javascript
|
576 | import {Transform} from "class-transformer";
|
577 | import * as moment from "moment";
|
578 | import {Moment} from "moment";
|
579 |
|
580 | export class Photo {
|
581 |
|
582 | id: number;
|
583 |
|
584 | @Transform(value => moment(value), { toClassOnly: true })
|
585 | date: Moment;
|
586 | }
|
587 | ```
|
588 |
|
589 | Now when you call `plainToClass` and send a plain representation of the Photo object,
|
590 | it will convert a date value in your photo object to moment date.
|
591 | `@Transform` decorator also supports groups and versioning.
|
592 |
|
593 | ## Working with generics
|
594 |
|
595 | Generics are not supported because TypeScript does not have good reflection abilities yet.
|
596 | Once TypeScript team provide us better runtime type reelection tools, generics will be implemented.
|
597 | There are some tweaks however you can use, that maybe can solve your problem.
|
598 | [Checkout this example.](https://github.com/pleerock/class-transformer/tree/master/sample/sample4-generics)
|
599 |
|
600 | ## How does it handle circular references?
|
601 |
|
602 | Circular references are ignored.
|
603 | For example, if you are transforming class `User` that contains property `photos` with type of `Photo`,
|
604 | and `Photo` contains link `user` to its parent `User`, then `user` will be ignored during transformation.
|
605 | Circular references are not ignored only during `classToClass` operation.
|
606 |
|
607 | ## Example with Angular2
|
608 |
|
609 | Lets say you want to download users and want them automatically to be mapped to the instances of `User` class.
|
610 |
|
611 | ```javascript
|
612 | import {plainToClass} from "class-transformer";
|
613 |
|
614 | this.http
|
615 | .get("users.json")
|
616 | .map(res => res.json())
|
617 | .map(res => plainToClass(User, res as Object[]))
|
618 | .subscribe(users => {
|
619 | // now "users" is type of User[] and each user have getName() and isAdult() methods available
|
620 | console.log(users);
|
621 | });
|
622 | ```
|
623 |
|
624 | You can also inject a class `ClassTransformer` as a service in `providers`, and use its methods.
|
625 |
|
626 | Example how to use with angular 2 in [plunker](http://plnkr.co/edit/Mja1ZYAjVySWASMHVB9R).
|
627 | Source code is [here](https://github.com/pleerock/class-transformer-demo).
|
628 |
|
629 | ## Samples
|
630 |
|
631 | Take a look on samples in [./sample](https://github.com/pleerock/class-transformer/tree/master/sample) for more examples of
|
632 | usages.
|
633 |
|
634 | ## Release notes
|
635 |
|
636 | See information about breaking changes and release notes [here](https://github.com/pleerock/class-transformer/tree/master/doc/release-notes.md).
|