- Notifications
You must be signed in to change notification settings - Fork519
Decorator-based transformation, serialization, and deserialization between objects and classes.
License
typestack/class-transformer
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Its ES6 and Typescript era. Nowadays you are working with classes and constructor objects more than ever.Class-transformer allows you to transform plain object to some instance of class and versa.Also it allows to serialize / deserialize object based on criteria.This tool is super useful on both frontend and backend.
Example how to use with angular 2 inplunker.Source code is availablehere.
- What is class-transformer
- Installation
- Methods
- Enforcing type-safe instance
- Working with nested objects
- Exposing getters and method return values
- Exposing properties with different names
- Skipping specific properties
- Skipping depend of operation
- Skipping all properties of the class
- Skipping private properties, or some prefixed properties
- Using groups to control excluded properties
- Using versioning to control exposed and excluded properties
- Сonverting date strings into Date objects
- Working with arrays
- Additional data transformation
- Other decorators
- Working with generics
- Implicit type conversion
- How does it handle circular references?
- Example with Angular2
- Samples
- Release notes
What is class-transformer⬆
In JavaScript there are two types of objects:
- plain (literal) objects
- class (constructor) objects
Plain objects are objects that are instances ofObject class.Sometimes they are calledliteral objects, when created via{} notation.Class objects are instances of classes with own defined constructor, properties and methods.Usually you define them viaclass notation.
So, what is the problem?
Sometimes you want to transform plain javascript object to the ES6classes you have.For example, if you are loading a json from your backend, some api or from a json file,and after youJSON.parse it you have a plain javascript object, not instance of class you have.
For example you have a list of users in yourusers.json that you are loading:
[ {"id":1,"firstName":"Johny","lastName":"Cage","age":27 }, {"id":2,"firstName":"Ismoil","lastName":"Somoni","age":50 }, {"id":3,"firstName":"Luke","lastName":"Dacascos","age":12 }]And you have aUser class:
exportclassUser{id:number;firstName:string;lastName:string;age:number;getName(){returnthis.firstName+' '+this.lastName;}isAdult(){returnthis.age>36&&this.age<60;}}
You are assuming that you are downloading users of typeUser fromusers.json file and may want to writefollowing code:
fetch('users.json').then((users:User[])=>{// you can use users here, and type hinting also will be available to you,// but users are not actually instances of User class// this means that you can't use methods of User class});
In this code you can useusers[0].id, you can also useusers[0].firstName andusers[0].lastName.However you cannot useusers[0].getName() orusers[0].isAdult() because "users" actually isarray of plain javascript objects, not instances of User object.You actually lied to compiler when you said that itsusers: User[].
So what to do? How to make ausers array of instances ofUser objects instead of plain javascript objects?Solution is to create new instances of User object and manually copy all properties to new objects.But things may go wrong very fast once you have a more complex object hierarchy.
Alternatives? Yes, you can use class-transformer. Purpose of this library is to help you to map your plain javascriptobjects to the instances of classes you have.
This library also great for models exposed in your APIs,because it provides a great tooling to control what your models are exposing in your API.Here is an example how it will look like:
fetch('users.json').then((users:Object[])=>{constrealUsers=plainToInstance(User,users);// now each user in realUsers is an instance of User class});
Now you can useusers[0].getName() andusers[0].isAdult() methods.
Installation⬆
Node.js⬆
Install module:
npm install class-transformer --savereflect-metadatashim is required, install it too:npm install reflect-metadata --saveand make sure to import it in a global place, like app.ts:
import'reflect-metadata';
ES6 features are used, if you are using old version of node.js you may need to install es6-shim:
npm install es6-shim --saveand import it in a global place like app.ts:
import'es6-shim';
Browser⬆
Install module:
npm install class-transformer --savereflect-metadatashim is required, install it too:npm install reflect-metadata --saveadd
<script>to reflect-metadata in the head of yourindex.html:<html><head><!-- ... --><scriptsrc="node_modules/reflect-metadata/Reflect.js"></script></head><!-- ... --></html>
If you are using angular 2 you should already have this shim installed.
If you are using system.js you may want to add this into
mapandpackageconfig:{"map": {"class-transformer":"node_modules/class-transformer" },"packages": {"class-transformer": {"main":"index.js","defaultExtension":"js" } }}
Methods⬆
plainToInstance⬆
This method transforms a plain javascript object to instance of specific class.
import{plainToInstance}from'class-transformer';letusers=plainToInstance(User,userJson);// to convert user plain object a single user. also supports arrays
plainToClassFromExist⬆
This method transforms a plain object into an instance using an already filled Object which is an instance of the target class.
constdefaultUser=newUser();defaultUser.role='user';letmixedUser=plainToClassFromExist(defaultUser,user);// mixed user should have the value role = user when no value is set otherwise.
instanceToPlain⬆
This method transforms your class object back to plain javascript object, that can beJSON.stringify later.
import{instanceToPlain}from'class-transformer';letphoto=instanceToPlain(photo);
instanceToInstance⬆
This method transforms your class object into a new instance of the class object.This may be treated as deep clone of your objects.
import{instanceToInstance}from'class-transformer';letphoto=instanceToInstance(photo);
You can also use anignoreDecorators option in transformation options to ignore all decorators your classes are using.
serialize⬆
You can serialize your model right to json usingserialize method:
import{serialize}from'class-transformer';letphoto=serialize(photo);
serialize works with both arrays and non-arrays.
deserialize and deserializeArray⬆
You can deserialize your model from json using thedeserialize method:
import{deserialize}from'class-transformer';letphoto=deserialize(Photo,photo);
To make deserialization work with arrays, use thedeserializeArray method:
import{deserializeArray}from'class-transformer';letphotos=deserializeArray(Photo,photos);
Enforcing type-safe instance⬆
The default behaviour of theplainToInstance method is to setall properties from the plain object,even those which are not specified in the class.
import{plainToInstance}from'class-transformer';classUser{id:number;firstName:string;lastName:string;}constfromPlainUser={unkownProp:'hello there',firstName:'Umed',lastName:'Khudoiberdiev',};console.log(plainToInstance(User,fromPlainUser));// User {// unkownProp: 'hello there',// firstName: 'Umed',// lastName: 'Khudoiberdiev',// }
If this behaviour does not suit your needs, you can use theexcludeExtraneousValues optionin theplainToInstance method whileexposing all your class properties as a requirement.
import{Expose,plainToInstance}from'class-transformer';classUser{ @Expose()id:number; @Expose()firstName:string; @Expose()lastName:string;}constfromPlainUser={unkownProp:'hello there',firstName:'Umed',lastName:'Khudoiberdiev',};console.log(plainToInstance(User,fromPlainUser,{excludeExtraneousValues:true}));// User {// id: undefined,// firstName: 'Umed',// lastName: 'Khudoiberdiev'// }
Working with nested objects⬆
When you are trying to transform objects that have nested objects,it's required to known what type of object you are trying to transform.Since Typescript does not have good reflection abilities yet,we should implicitly specify what type of object each property contain.This is done using@Type decorator.
Lets say we have an album with photos.And we are trying to convert album plain object to class object:
import{Type,plainToInstance}from'class-transformer';exportclassAlbum{id:number;name:string; @Type(()=>Photo)photos:Photo[];}exportclassPhoto{id:number;filename:string;}letalbum=plainToInstance(Album,albumJson);// now album is Album object with Photo objects inside
Providing more than one type option⬆
In case the nested object can be of different types, you can provide an additional options object,that specifies a discriminator. The discriminator option must define aproperty that holds the subtypename for the object and the possiblesubTypes that the nested object can converted to. A sub typehas avalue, that holds the constructor of the Type and thename, that can match with thepropertyof the discriminator.
Lets say we have an album that has a top photo. But this photo can be of certain different types.And we are trying to convert album plain object to class object. The plain object input has to definethe additional property__type. This property is removed during transformation by default:
JSON input:
{"id":1,"name":"foo","topPhoto": {"id":9,"filename":"cool_wale.jpg","depth":1245,"__type":"underwater" }}import{Type,plainToInstance}from'class-transformer';exportabstractclassPhoto{id:number;filename:string;}exportclassLandscapeextendsPhoto{panorama:boolean;}exportclassPortraitextendsPhoto{person:Person;}exportclassUnderWaterextendsPhoto{depth:number;}exportclassAlbum{id:number;name:string; @Type(()=>Photo,{discriminator:{property:'__type',subTypes:[{value:Landscape,name:'landscape'},{value:Portrait,name:'portrait'},{value:UnderWater,name:'underwater'},],},})topPhoto:Landscape|Portrait|UnderWater;}letalbum=plainToInstance(Album,albumJson);// now album is Album object with a UnderWater object without `__type` property.
Hint: The same applies for arrays with different sub types. Moreover you can specifykeepDiscriminatorProperty: truein the options to keep the discriminator property also inside your resulting class.
Exposing getters and method return values⬆
You can expose what your getter or method return by setting an@Expose() decorator to those getters or methods:
import{Expose}from'class-transformer';exportclassUser{id:number;firstName:string;lastName:string;password:string; @Expose()getname(){returnthis.firstName+' '+this.lastName;} @Expose()getFullName(){returnthis.firstName+' '+this.lastName;}}
Exposing properties with different names⬆
If you want to expose some of the properties with a different name,you can do that by specifying aname option to@Expose decorator:
import{Expose}from'class-transformer';exportclassUser{ @Expose({name:'uid'})id:number;firstName:string;lastName:string; @Expose({name:'secretKey'})password:string; @Expose({name:'fullName'})getFullName(){returnthis.firstName+' '+this.lastName;}}
Skipping specific properties⬆
Sometimes you want to skip some properties during transformation.This can be done using@Exclude decorator:
import{Exclude}from'class-transformer';exportclassUser{id:number;email:string; @Exclude()password:string;}
Now when you transform a User, thepassword property will be skipped and not be included in the transformed result.
Skipping depend of operation⬆
You can control on what operation you will exclude a property. UsetoClassOnly ortoPlainOnly options:
import{Exclude}from'class-transformer';exportclassUser{id:number;email:string; @Exclude({toPlainOnly:true})password:string;}
Nowpassword property will be excluded only duringinstanceToPlain operation. Vice versa, use thetoClassOnly option.
Skipping all properties of the class⬆
You can skip all properties of the class, and expose only those are needed explicitly:
import{Exclude,Expose}from'class-transformer';@Exclude()exportclassUser{ @Expose()id:number; @Expose()email:string;password:string;}
Nowid andemail will be exposed, and password will be excluded during transformation.Alternatively, you can set exclusion strategy during transformation:
import{instanceToPlain}from'class-transformer';letphoto=instanceToPlain(photo,{strategy:'excludeAll'});
In this case you don't need to@Exclude() a whole class.
Skipping private properties, or some prefixed properties⬆
If you name your private properties with a prefix, lets say with_,then you can exclude such properties from transformation too:
import{instanceToPlain}from'class-transformer';letphoto=instanceToPlain(photo,{excludePrefixes:['_']});
This will skip all properties that start with_ prefix.You can pass any number of prefixes and all properties that begin with these prefixes will be ignored.For example:
import{Expose,instanceToPlain}from'class-transformer';exportclassUser{id:number;private_firstName:string;private_lastName:string;_password:string;setName(firstName:string,lastName:string){this._firstName=firstName;this._lastName=lastName;} @Expose()getname(){returnthis._firstName+' '+this._lastName;}}constuser=newUser();user.id=1;user.setName('Johny','Cage');user._password='123';constplainUser=instanceToPlain(user,{excludePrefixes:['_']});// here plainUser will be equal to// { id: 1, name: "Johny Cage" }
Using groups to control excluded properties⬆
You can use groups to control what data will be exposed and what will not be:
import{Exclude,Expose,instanceToPlain}from'class-transformer';exportclassUser{id:number;name:string; @Expose({groups:['user','admin']})// this means that this data will be exposed only to users and adminsemail:string; @Expose({groups:['user']})// this means that this data will be exposed only to userspassword:string;}letuser1=instanceToPlain(user,{groups:['user']});// will contain id, name, email and passwordletuser2=instanceToPlain(user,{groups:['admin']});// will contain id, name and email
Using versioning to control exposed and excluded properties⬆
If you are building an API that has different versions, class-transformer has extremely useful tools for that.You can control which properties of your model should be exposed or excluded in what version. Example:
import{Exclude,Expose,instanceToPlain}from'class-transformer';exportclassUser{id:number;name:string; @Expose({since:0.7,until:1})// this means that this property will be exposed for version starting from 0.7 until 1email:string; @Expose({since:2.1})// this means that this property will be exposed for version starting from 2.1password:string;}letuser1=instanceToPlain(user,{version:0.5});// will contain id and nameletuser2=instanceToPlain(user,{version:0.7});// will contain id, name and emailletuser3=instanceToPlain(user,{version:1});// will contain id and nameletuser4=instanceToPlain(user,{version:2});// will contain id and nameletuser5=instanceToPlain(user,{version:2.1});// will contain id, name and password
Сonverting date strings into Date objects⬆
Sometimes you have a Date in your plain javascript object received in a string format.And you want to create a real javascript Date object from it.You can do it simply by passing a Date object to the@Type decorator:
import{Type}from'class-transformer';exportclassUser{id:number;email:string;password:string; @Type(()=>Date)registrationDate:Date;}
Same technique can be used withNumber,String,Booleanprimitive types when you want to convert your values into these types.
Working with arrays⬆
When you are using arrays you must provide a type of the object that array contains.This type, you specify in a@Type() decorator:
import{Type}from'class-transformer';exportclassPhoto{id:number;name:string; @Type(()=>Album)albums:Album[];}
You can also use custom array types:
import{Type}from'class-transformer';exportclassAlbumCollectionextendsArray<Album>{// custom array functions ...}exportclassPhoto{id:number;name:string; @Type(()=>Album)albums:AlbumCollection;}
Library will handle proper transformation automatically.
ES6 collectionsSet andMap also require the@Type decorator:
exportclassSkill{name:string;}exportclassWeapon{name:string;range:number;}exportclassPlayer{name:string; @Type(()=>Skill)skills:Set<Skill>; @Type(()=>Weapon)weapons:Map<string,Weapon>;}
Additional data transformation⬆
Basic usage⬆
You can perform additional data transformation using@Transform decorator.For example, you want to make yourDate object to be amoment object when you aretransforming object from plain to class:
import{Transform}from'class-transformer';import*asmomentfrom'moment';import{Moment}from'moment';exportclassPhoto{id:number; @Type(()=>Date) @Transform(({ value})=>moment(value),{toClassOnly:true})date:Moment;}
Now when you callplainToInstance and send a plain representation of the Photo object,it will convert a date value in your photo object to moment date.@Transform decorator also supports groups and versioning.
Advanced usage⬆
The@Transform decorator is given more arguments to let you configure how you want the transformation to be done.
@Transform(({ value, key, obj, type})=>value)
| Argument | Description |
|---|---|
value | The property value before the transformation. |
key | The name of the transformed property. |
obj | The transformation source object. |
type | The transformation type. |
options | The options object passed to the transformation method. |
Other decorators⬆
| Signature | Example | Description |
|---|---|---|
@TransformClassToPlain | @TransformClassToPlain({ groups: ["user"] }) | Transform the method return with instanceToPlain and expose the properties on the class. |
@TransformClassToClass | @TransformClassToClass({ groups: ["user"] }) | Transform the method return with instanceToInstance and expose the properties on the class. |
@TransformPlainToClass | @TransformPlainToClass(User, { groups: ["user"] }) | Transform the method return with plainToInstance and expose the properties on the class. |
The above decorators accept one optional argument:ClassTransformOptions - The transform options like groups, version, name
An example:
@Exclude()classUser{id:number; @Expose()firstName:string; @Expose()lastName:string; @Expose({groups:['user.email']})email:string;password:string;}classUserController{ @TransformClassToPlain({groups:['user.email']})getUser(){constuser=newUser();user.firstName='Snir';user.lastName='Segal';user.password='imnosuperman';returnuser;}}constcontroller=newUserController();constuser=controller.getUser();
theuser variable will contain only firstName,lastName, email properties because they arethe exposed variables. email property is also exposed because we metioned the group "user.email".
Working with generics⬆
Generics are not supported because TypeScript does not have good reflection abilities yet.Once TypeScript team provide us better runtime type reflection tools, generics will be implemented.There are some tweaks however you can use, that maybe can solve your problem.Checkout this example.
Implicit type conversion⬆
NOTE If you use class-validator together with class-transformer you propably DON'T want to enable this function.
Enables automatic conversion between built-in types based on type information provided by Typescript. Disabled by default.
import{IsString}from'class-validator';classMyPayload{ @IsString()prop:string;}constresult1=plainToInstance(MyPayload,{prop:1234},{enableImplicitConversion:true});constresult2=plainToInstance(MyPayload,{prop:1234},{enableImplicitConversion:false});/** * result1 will be `{ prop: "1234" }` - notice how the prop value has been converted to string. * result2 will be `{ prop: 1234 }` - default behaviour */
How does it handle circular references?⬆
Circular references are ignored.For example, if you are transforming classUser that contains propertyphotos with type ofPhoto,andPhoto contains linkuser to its parentUser, thenuser will be ignored during transformation.Circular references are not ignored only duringinstanceToInstance operation.
Example with Angular2⬆
Lets say you want to download users and want them automatically to be mapped to the instances ofUser class.
import{plainToInstance}from'class-transformer';this.http.get('users.json').map(res=>res.json()).map(res=>plainToInstance(User,resasObject[])).subscribe(users=>{// now "users" is type of User[] and each user has getName() and isAdult() methods availableconsole.log(users);});
You can also inject a classClassTransformer as a service inproviders, and use its methods.
Example how to use with angular 2 inplunker.Source code ishere.
Samples⬆
Take a look on samples in./sample for more examples ofusages.
Release notes⬆
See information about breaking changes and release noteshere.
About
Decorator-based transformation, serialization, and deserialization between objects and classes.
Topics
Resources
License
Contributing
Uh oh!
There was an error while loading.Please reload this page.