1667861760
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 in plunker. Source code is available here.
In JavaScript there are two types of objects:
Plain objects are objects that are instances of Object
class. Sometimes they are called literal objects, when created via {}
notation. Class objects are instances of classes with own defined constructor, properties and methods. Usually you define them via class
notation.
So, what is the problem?
Sometimes you want to transform plain javascript object to the ES6 classes you have. For example, if you are loading a json from your backend, some api or from a json file, and after you JSON.parse
it you have a plain javascript object, not instance of class you have.
For example you have a list of users in your users.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 a User
class:
export class User {
id: number;
firstName: string;
lastName: string;
age: number;
getName() {
return this.firstName + ' ' + this.lastName;
}
isAdult() {
return this.age > 36 && this.age < 60;
}
}
You are assuming that you are downloading users of type User
from users.json
file and may want to write following 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 use users[0].id
, you can also use users[0].firstName
and users[0].lastName
. However you cannot use users[0].getName()
or users[0].isAdult()
because "users" actually is array of plain javascript objects, not instances of User object. You actually lied to compiler when you said that its users: User[]
.
So what to do? How to make a users
array of instances of User
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 javascript objects 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[]) => {
const realUsers = plainToClass(User, users);
// now each user in realUsers is an instance of User class
});
Now you can use users[0].getName()
and users[0].isAdult()
methods.
Install module:
npm install class-transformer --save
reflect-metadata
shim is required, install it too:
npm install reflect-metadata --save
and 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 --save
and import it in a global place like app.ts:
import 'es6-shim';
Install module:
npm install class-transformer --save
reflect-metadata
shim is required, install it too:
npm install reflect-metadata --save
add <script>
to reflect-metadata in the head of your index.html
:
<html>
<head>
<!-- ... -->
<script src="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 map
and package
config:
{
"map": {
"class-transformer": "node_modules/class-transformer"
},
"packages": {
"class-transformer": { "main": "index.js", "defaultExtension": "js" }
}
}
This method transforms a plain javascript object to instance of specific class.
import { plainToClass } from 'class-transformer';
let users = plainToClass(User, userJson); // to convert user plain object a single user. also supports arrays
This method transforms a plain object into an instance using an already filled Object which is an instance of the target class.
const defaultUser = new User();
defaultUser.role = 'user';
let mixedUser = plainToClassFromExist(defaultUser, user); // mixed user should have the value role = user when no value is set otherwise.
This method transforms your class object back to plain javascript object, that can be JSON.stringify
later.
import { classToPlain } from 'class-transformer';
let photo = classToPlain(photo);
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 { classToClass } from 'class-transformer';
let photo = classToClass(photo);
You can also use an ignoreDecorators
option in transformation options to ignore all decorators you classes is using.
You can serialize your model right to json using serialize
method:
import { serialize } from 'class-transformer';
let photo = serialize(photo);
serialize
works with both arrays and non-arrays.
You can deserialize your model from json using the deserialize
method:
import { deserialize } from 'class-transformer';
let photo = deserialize(Photo, photo);
To make deserialization work with arrays, use the deserializeArray
method:
import { deserializeArray } from 'class-transformer';
let photos = deserializeArray(Photo, photos);
The default behaviour of the plainToClass
method is to set all properties from the plain object, even those which are not specified in the class.
import { plainToClass } from 'class-transformer';
class User {
id: number;
firstName: string;
lastName: string;
}
const fromPlainUser = {
unkownProp: 'hello there',
firstName: 'Umed',
lastName: 'Khudoiberdiev',
};
console.log(plainToClass(User, fromPlainUser));
// User {
// unkownProp: 'hello there',
// firstName: 'Umed',
// lastName: 'Khudoiberdiev',
// }
If this behaviour does not suit your needs, you can use the excludeExtraneousValues
option in the plainToClass
method while exposing all your class properties as a requirement.
import { Expose, plainToClass } from 'class-transformer';
class User {
@Expose() id: number;
@Expose() firstName: string;
@Expose() lastName: string;
}
const fromPlainUser = {
unkownProp: 'hello there',
firstName: 'Umed',
lastName: 'Khudoiberdiev',
};
console.log(plainToClass(User, fromPlainUser, { excludeExtraneousValues: true }));
// User {
// id: undefined,
// firstName: 'Umed',
// lastName: 'Khudoiberdiev'
// }
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, plainToClass } from 'class-transformer';
export class Album {
id: number;
name: string;
@Type(() => Photo)
photos: Photo[];
}
export class Photo {
id: number;
filename: string;
}
let album = plainToClass(Album, albumJson);
// now album is Album object with Photo objects inside
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 a property
that holds the subtype name for the object and the possible subTypes
that the nested object can converted to. A sub type has a value
, that holds the constructor of the Type and the name
, that can match with the property
of 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 define the 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, plainToClass } from 'class-transformer';
export abstract class Photo {
id: number;
filename: string;
}
export class Landscape extends Photo {
panorama: boolean;
}
export class Portrait extends Photo {
person: Person;
}
export class UnderWater extends Photo {
depth: number;
}
export class Album {
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;
}
let album = plainToClass(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 specify keepDiscriminatorProperty: true
in the options to keep the discriminator property also inside your resulting class.
You can expose what your getter or method return by setting an @Expose()
decorator to those getters or methods:
import { Expose } from 'class-transformer';
export class User {
id: number;
firstName: string;
lastName: string;
password: string;
@Expose()
get name() {
return this.firstName + ' ' + this.lastName;
}
@Expose()
getFullName() {
return this.firstName + ' ' + this.lastName;
}
}
If you want to expose some of the properties with a different name, you can do that by specifying a name
option to @Expose
decorator:
import { Expose } from 'class-transformer';
export class User {
@Expose({ name: 'uid' })
id: number;
firstName: string;
lastName: string;
@Expose({ name: 'secretKey' })
password: string;
@Expose({ name: 'fullName' })
getFullName() {
return this.firstName + ' ' + this.lastName;
}
}
Sometimes you want to skip some properties during transformation. This can be done using @Exclude
decorator:
import { Exclude } from 'class-transformer';
export class User {
id: number;
email: string;
@Exclude()
password: string;
}
Now when you transform a User, the password
property will be skipped and not be included in the transformed result.
You can control on what operation you will exclude a property. Use toClassOnly
or toPlainOnly
options:
import { Exclude } from 'class-transformer';
export class User {
id: number;
email: string;
@Exclude({ toPlainOnly: true })
password: string;
}
Now password
property will be excluded only during classToPlain
operation. Vice versa, use the toClassOnly
option.
You can skip all properties of the class, and expose only those are needed explicitly:
import { Exclude, Expose } from 'class-transformer';
@Exclude()
export class User {
@Expose()
id: number;
@Expose()
email: string;
password: string;
}
Now id
and email
will be exposed, and password will be excluded during transformation. Alternatively, you can set exclusion strategy during transformation:
import { classToPlain } from 'class-transformer';
let photo = classToPlain(photo, { strategy: 'excludeAll' });
In this case you don't need to @Exclude()
a whole class.
If you name your private properties with a prefix, lets say with _
, then you can exclude such properties from transformation too:
import { classToPlain } from 'class-transformer';
let photo = classToPlain(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, classToPlain } from 'class-transformer';
export class User {
id: number;
private _firstName: string;
private _lastName: string;
_password: string;
setName(firstName: string, lastName: string) {
this._firstName = firstName;
this._lastName = lastName;
}
@Expose()
get name() {
return this._firstName + ' ' + this._lastName;
}
}
const user = new User();
user.id = 1;
user.setName('Johny', 'Cage');
user._password = '123';
const plainUser = classToPlain(user, { excludePrefixes: ['_'] });
// here plainUser will be equal to
// { id: 1, name: "Johny Cage" }
You can use groups to control what data will be exposed and what will not be:
import { Exclude, Expose, classToPlain } from 'class-transformer';
export class User {
id: number;
name: string;
@Expose({ groups: ['user', 'admin'] }) // this means that this data will be exposed only to users and admins
email: string;
@Expose({ groups: ['user'] }) // this means that this data will be exposed only to users
password: string;
}
let user1 = classToPlain(user, { groups: ['user'] }); // will contain id, name, email and password
let user2 = classToPlain(user, { groups: ['admin'] }); // will contain id, name and email
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, classToPlain } from 'class-transformer';
export class User {
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 1
email: string;
@Expose({ since: 2.1 }) // this means that this property will be exposed for version starting from 2.1
password: string;
}
let user1 = classToPlain(user, { version: 0.5 }); // will contain id and name
let user2 = classToPlain(user, { version: 0.7 }); // will contain id, name and email
let user3 = classToPlain(user, { version: 1 }); // will contain id and name
let user4 = classToPlain(user, { version: 2 }); // will contain id and name
let user5 = classToPlain(user, { version: 2.1 }); // will contain id, name and password
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';
export class User {
id: number;
email: string;
password: string;
@Type(() => Date)
registrationDate: Date;
}
Same technique can be used with Number
, String
, Boolean
primitive types when you want to convert your values into these types.
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';
export class Photo {
id: number;
name: string;
@Type(() => Album)
albums: Album[];
}
You can also use custom array types:
import { Type } from 'class-transformer';
export class AlbumCollection extends Array<Album> {
// custom array functions ...
}
export class Photo {
id: number;
name: string;
@Type(() => Album)
albums: AlbumCollection;
}
Library will handle proper transformation automatically.
ES6 collections Set
and Map
also require the @Type
decorator:
export class Skill {
name: string;
}
export class Weapon {
name: string;
range: number;
}
export class Player {
name: string;
@Type(() => Skill)
skills: Set<Skill>;
@Type(() => Weapon)
weapons: Map<string, Weapon>;
}
You can perform additional data transformation using @Transform
decorator. For example, you want to make your Date
object to be a moment
object when you are transforming object from plain to class:
import { Transform } from 'class-transformer';
import * as moment from 'moment';
import { Moment } from 'moment';
export class Photo {
id: number;
@Type(() => Date)
@Transform(({ value }) => moment(value), { toClassOnly: true })
date: Moment;
}
Now when you call plainToClass
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.
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. |
Signature | Example | Description |
---|---|---|
@TransformClassToPlain | @TransformClassToPlain({ groups: ["user"] }) | Transform the method return with classToPlain and expose the properties on the class. |
@TransformClassToClass | @TransformClassToClass({ groups: ["user"] }) | Transform the method return with classToClass and expose the properties on the class. |
@TransformPlainToClass | @TransformPlainToClass(User, { groups: ["user"] }) | Transform the method return with plainToClass 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()
class User {
id: number;
@Expose()
firstName: string;
@Expose()
lastName: string;
@Expose({ groups: ['user.email'] })
email: string;
password: string;
}
class UserController {
@TransformClassToPlain({ groups: ['user.email'] })
getUser() {
const user = new User();
user.firstName = 'Snir';
user.lastName = 'Segal';
user.password = 'imnosuperman';
return user;
}
}
const controller = new UserController();
const user = controller.getUser();
the user
variable will contain only firstName,lastName, email properties because they are the exposed variables. email property is also exposed because we metioned the group "user.email".
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.
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';
class MyPayload {
@IsString()
prop: string;
}
const result1 = plainToClass(MyPayload, { prop: 1234 }, { enableImplicitConversion: true });
const result2 = plainToClass(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
*/
Circular references are ignored. For example, if you are transforming class User
that contains property photos
with type of Photo
, and Photo
contains link user
to its parent User
, then user
will be ignored during transformation. Circular references are not ignored only during classToClass
operation.
Lets say you want to download users and want them automatically to be mapped to the instances of User
class.
import { plainToClass } from 'class-transformer';
this.http
.get('users.json')
.map(res => res.json())
.map(res => plainToClass(User, res as Object[]))
.subscribe(users => {
// now "users" is type of User[] and each user has getName() and isAdult() methods available
console.log(users);
});
You can also inject a class ClassTransformer
as a service in providers
, and use its methods.
Example how to use with angular 2 in plunker. Source code is here.
Take a look on samples in ./sample for more examples of usages.
See information about breaking changes and release notes here.
Author: Typestack
Source Code: https://github.com/typestack/class-transformer
License: MIT license
#typescript #class #transformer #objects
1662107520
Superdom
You have dom
. It has all the DOM virtually within it. Use that power:
// Fetch all the page links
let links = dom.a.href;
// Links open in a new tab
dom.a.target = '_blank';
Only for modern browsers
Simply use the CDN via unpkg.com:
<script src="https://unpkg.com/superdom@1"></script>
Or use npm or bower:
npm|bower install superdom --save
It always returns an array with the matched elements. Get all the elements that match the selector:
// Simple element selector into an array
let allLinks = dom.a;
// Loop straight on the selection
dom.a.forEach(link => { ... });
// Combined selector
let importantLinks = dom['a.important'];
There are also some predetermined elements, such as id
, class
and attr
:
// Select HTML Elements by id:
let main = dom.id.main;
// by class:
let buttons = dom.class.button;
// or by attribute:
let targeted = dom.attr.target;
let targeted = dom.attr['target="_blank"'];
Use it as a function or a tagged template literal to generate DOM fragments:
// Not a typo; tagged template literals
let link = dom`<a href="https://google.com/">Google</a>`;
// It is the same as
let link = dom('<a href="https://google.com/">Google</a>');
Delete a piece of the DOM
// Delete all of the elements with the class .google
delete dom.class.google; // Is this an ad-block rule?
You can easily manipulate attributes right from the dom
node. There are some aliases that share the syntax of the attributes such as html
and text
(aliases for innerHTML
and textContent
). There are others that travel through the dom such as parent
(alias for parentNode) and children
. Finally, class
behaves differently as explained below.
The fetching will always return an array with the element for each of the matched nodes (or undefined if not there):
// Retrieve all the urls from the page
let urls = dom.a.href; // #attr-list
// ['https://google.com', 'https://facebook.com/', ...]
// Get an array of the h2 contents (alias of innerHTML)
let h2s = dom.h2.html; // #attr-alias
// ['Level 2 header', 'Another level 2 header', ...]
// Get whether any of the attributes has the value "_blank"
let hasBlank = dom.class.cta.target._blank; // #attr-value
// true/false
You also use these:
innerHTML
): retrieve a list of the htmlstextContent
): retrieve a list of the htmlsparentNode
): travel up one level// Set target="_blank" to all links
dom.a.target = '_blank'; // #attr-set
dom.class.tableofcontents.html = `
<ul class="tableofcontents">
${dom.h2.map(h2 => `
<li>
<a href="#${h2.id}">
${h2.innerHTML}
</a>
</li>
`).join('')}
</ul>
`;
To delete an attribute use the delete
keyword:
// Remove all urls from the page
delete dom.a.href;
// Remove all ids
delete dom.a.id;
It provides an easy way to manipulate the classes.
To retrieve whether a particular class is present or not:
// Get an array with true/false for a single class
let isTest = dom.a.class.test; // #class-one
For a general method to retrieve all classes you can do:
// Get a list of the classes of each matched element
let arrays = dom.a.class; // #class-arrays
// [['important'], ['button', 'cta'], ...]
// If you want a plain list with all of the classes:
let flatten = dom.a.class._flat; // #class-flat
// ['important', 'button', 'cta', ...]
// And if you just want an string with space-separated classes:
let text = dom.a.class._text; // #class-text
// 'important button cta ...'
// Add the class 'test' (different ways)
dom.a.class.test = true; // #class-make-true
dom.a.class = 'test'; // #class-push
// Remove the class 'test'
dom.a.class.test = false; // #class-make-false
Did we say it returns a simple array?
dom.a.forEach(link => link.innerHTML = 'I am a link');
But what an interesting array it is; indeed we are also proxy'ing it so you can manipulate its sub-elements straight from the selector:
// Replace all of the link's html with 'I am a link'
dom.a.html = 'I am a link';
Of course we might want to manipulate them dynamically depending on the current value. Just pass it a function:
// Append ' ^_^' to all of the links in the page
dom.a.html = html => html + ' ^_^';
// Same as this:
dom.a.forEach(link => link.innerHTML = link.innerHTML + ' ^_^');
Note: this won't work
dom.a.html += ' ^_^';
for more than 1 match (for reasons)
Or get into genetics to manipulate the attributes:
dom.a.attr.target = '_blank';
// Only to external sites:
let isOwnPage = el => /^https?\:\/\/mypage\.com/.test(el.getAttribute('href'));
dom.a.attr.target = (prev, i, element) => isOwnPage(element) ? '' : '_blank';
You can also handle and trigger events:
// Handle click events for all <a>
dom.a.on.click = e => ...;
// Trigger click event for all <a>
dom.a.trigger.click;
We are using Jest as a Grunt task for testing. Install Jest and run in the terminal:
grunt watch
Author: franciscop
Source Code: https://github.com/franciscop/superdom
License: MIT license
1667861760
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 in plunker. Source code is available here.
In JavaScript there are two types of objects:
Plain objects are objects that are instances of Object
class. Sometimes they are called literal objects, when created via {}
notation. Class objects are instances of classes with own defined constructor, properties and methods. Usually you define them via class
notation.
So, what is the problem?
Sometimes you want to transform plain javascript object to the ES6 classes you have. For example, if you are loading a json from your backend, some api or from a json file, and after you JSON.parse
it you have a plain javascript object, not instance of class you have.
For example you have a list of users in your users.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 a User
class:
export class User {
id: number;
firstName: string;
lastName: string;
age: number;
getName() {
return this.firstName + ' ' + this.lastName;
}
isAdult() {
return this.age > 36 && this.age < 60;
}
}
You are assuming that you are downloading users of type User
from users.json
file and may want to write following 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 use users[0].id
, you can also use users[0].firstName
and users[0].lastName
. However you cannot use users[0].getName()
or users[0].isAdult()
because "users" actually is array of plain javascript objects, not instances of User object. You actually lied to compiler when you said that its users: User[]
.
So what to do? How to make a users
array of instances of User
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 javascript objects 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[]) => {
const realUsers = plainToClass(User, users);
// now each user in realUsers is an instance of User class
});
Now you can use users[0].getName()
and users[0].isAdult()
methods.
Install module:
npm install class-transformer --save
reflect-metadata
shim is required, install it too:
npm install reflect-metadata --save
and 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 --save
and import it in a global place like app.ts:
import 'es6-shim';
Install module:
npm install class-transformer --save
reflect-metadata
shim is required, install it too:
npm install reflect-metadata --save
add <script>
to reflect-metadata in the head of your index.html
:
<html>
<head>
<!-- ... -->
<script src="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 map
and package
config:
{
"map": {
"class-transformer": "node_modules/class-transformer"
},
"packages": {
"class-transformer": { "main": "index.js", "defaultExtension": "js" }
}
}
This method transforms a plain javascript object to instance of specific class.
import { plainToClass } from 'class-transformer';
let users = plainToClass(User, userJson); // to convert user plain object a single user. also supports arrays
This method transforms a plain object into an instance using an already filled Object which is an instance of the target class.
const defaultUser = new User();
defaultUser.role = 'user';
let mixedUser = plainToClassFromExist(defaultUser, user); // mixed user should have the value role = user when no value is set otherwise.
This method transforms your class object back to plain javascript object, that can be JSON.stringify
later.
import { classToPlain } from 'class-transformer';
let photo = classToPlain(photo);
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 { classToClass } from 'class-transformer';
let photo = classToClass(photo);
You can also use an ignoreDecorators
option in transformation options to ignore all decorators you classes is using.
You can serialize your model right to json using serialize
method:
import { serialize } from 'class-transformer';
let photo = serialize(photo);
serialize
works with both arrays and non-arrays.
You can deserialize your model from json using the deserialize
method:
import { deserialize } from 'class-transformer';
let photo = deserialize(Photo, photo);
To make deserialization work with arrays, use the deserializeArray
method:
import { deserializeArray } from 'class-transformer';
let photos = deserializeArray(Photo, photos);
The default behaviour of the plainToClass
method is to set all properties from the plain object, even those which are not specified in the class.
import { plainToClass } from 'class-transformer';
class User {
id: number;
firstName: string;
lastName: string;
}
const fromPlainUser = {
unkownProp: 'hello there',
firstName: 'Umed',
lastName: 'Khudoiberdiev',
};
console.log(plainToClass(User, fromPlainUser));
// User {
// unkownProp: 'hello there',
// firstName: 'Umed',
// lastName: 'Khudoiberdiev',
// }
If this behaviour does not suit your needs, you can use the excludeExtraneousValues
option in the plainToClass
method while exposing all your class properties as a requirement.
import { Expose, plainToClass } from 'class-transformer';
class User {
@Expose() id: number;
@Expose() firstName: string;
@Expose() lastName: string;
}
const fromPlainUser = {
unkownProp: 'hello there',
firstName: 'Umed',
lastName: 'Khudoiberdiev',
};
console.log(plainToClass(User, fromPlainUser, { excludeExtraneousValues: true }));
// User {
// id: undefined,
// firstName: 'Umed',
// lastName: 'Khudoiberdiev'
// }
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, plainToClass } from 'class-transformer';
export class Album {
id: number;
name: string;
@Type(() => Photo)
photos: Photo[];
}
export class Photo {
id: number;
filename: string;
}
let album = plainToClass(Album, albumJson);
// now album is Album object with Photo objects inside
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 a property
that holds the subtype name for the object and the possible subTypes
that the nested object can converted to. A sub type has a value
, that holds the constructor of the Type and the name
, that can match with the property
of 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 define the 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, plainToClass } from 'class-transformer';
export abstract class Photo {
id: number;
filename: string;
}
export class Landscape extends Photo {
panorama: boolean;
}
export class Portrait extends Photo {
person: Person;
}
export class UnderWater extends Photo {
depth: number;
}
export class Album {
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;
}
let album = plainToClass(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 specify keepDiscriminatorProperty: true
in the options to keep the discriminator property also inside your resulting class.
You can expose what your getter or method return by setting an @Expose()
decorator to those getters or methods:
import { Expose } from 'class-transformer';
export class User {
id: number;
firstName: string;
lastName: string;
password: string;
@Expose()
get name() {
return this.firstName + ' ' + this.lastName;
}
@Expose()
getFullName() {
return this.firstName + ' ' + this.lastName;
}
}
If you want to expose some of the properties with a different name, you can do that by specifying a name
option to @Expose
decorator:
import { Expose } from 'class-transformer';
export class User {
@Expose({ name: 'uid' })
id: number;
firstName: string;
lastName: string;
@Expose({ name: 'secretKey' })
password: string;
@Expose({ name: 'fullName' })
getFullName() {
return this.firstName + ' ' + this.lastName;
}
}
Sometimes you want to skip some properties during transformation. This can be done using @Exclude
decorator:
import { Exclude } from 'class-transformer';
export class User {
id: number;
email: string;
@Exclude()
password: string;
}
Now when you transform a User, the password
property will be skipped and not be included in the transformed result.
You can control on what operation you will exclude a property. Use toClassOnly
or toPlainOnly
options:
import { Exclude } from 'class-transformer';
export class User {
id: number;
email: string;
@Exclude({ toPlainOnly: true })
password: string;
}
Now password
property will be excluded only during classToPlain
operation. Vice versa, use the toClassOnly
option.
You can skip all properties of the class, and expose only those are needed explicitly:
import { Exclude, Expose } from 'class-transformer';
@Exclude()
export class User {
@Expose()
id: number;
@Expose()
email: string;
password: string;
}
Now id
and email
will be exposed, and password will be excluded during transformation. Alternatively, you can set exclusion strategy during transformation:
import { classToPlain } from 'class-transformer';
let photo = classToPlain(photo, { strategy: 'excludeAll' });
In this case you don't need to @Exclude()
a whole class.
If you name your private properties with a prefix, lets say with _
, then you can exclude such properties from transformation too:
import { classToPlain } from 'class-transformer';
let photo = classToPlain(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, classToPlain } from 'class-transformer';
export class User {
id: number;
private _firstName: string;
private _lastName: string;
_password: string;
setName(firstName: string, lastName: string) {
this._firstName = firstName;
this._lastName = lastName;
}
@Expose()
get name() {
return this._firstName + ' ' + this._lastName;
}
}
const user = new User();
user.id = 1;
user.setName('Johny', 'Cage');
user._password = '123';
const plainUser = classToPlain(user, { excludePrefixes: ['_'] });
// here plainUser will be equal to
// { id: 1, name: "Johny Cage" }
You can use groups to control what data will be exposed and what will not be:
import { Exclude, Expose, classToPlain } from 'class-transformer';
export class User {
id: number;
name: string;
@Expose({ groups: ['user', 'admin'] }) // this means that this data will be exposed only to users and admins
email: string;
@Expose({ groups: ['user'] }) // this means that this data will be exposed only to users
password: string;
}
let user1 = classToPlain(user, { groups: ['user'] }); // will contain id, name, email and password
let user2 = classToPlain(user, { groups: ['admin'] }); // will contain id, name and email
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, classToPlain } from 'class-transformer';
export class User {
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 1
email: string;
@Expose({ since: 2.1 }) // this means that this property will be exposed for version starting from 2.1
password: string;
}
let user1 = classToPlain(user, { version: 0.5 }); // will contain id and name
let user2 = classToPlain(user, { version: 0.7 }); // will contain id, name and email
let user3 = classToPlain(user, { version: 1 }); // will contain id and name
let user4 = classToPlain(user, { version: 2 }); // will contain id and name
let user5 = classToPlain(user, { version: 2.1 }); // will contain id, name and password
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';
export class User {
id: number;
email: string;
password: string;
@Type(() => Date)
registrationDate: Date;
}
Same technique can be used with Number
, String
, Boolean
primitive types when you want to convert your values into these types.
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';
export class Photo {
id: number;
name: string;
@Type(() => Album)
albums: Album[];
}
You can also use custom array types:
import { Type } from 'class-transformer';
export class AlbumCollection extends Array<Album> {
// custom array functions ...
}
export class Photo {
id: number;
name: string;
@Type(() => Album)
albums: AlbumCollection;
}
Library will handle proper transformation automatically.
ES6 collections Set
and Map
also require the @Type
decorator:
export class Skill {
name: string;
}
export class Weapon {
name: string;
range: number;
}
export class Player {
name: string;
@Type(() => Skill)
skills: Set<Skill>;
@Type(() => Weapon)
weapons: Map<string, Weapon>;
}
You can perform additional data transformation using @Transform
decorator. For example, you want to make your Date
object to be a moment
object when you are transforming object from plain to class:
import { Transform } from 'class-transformer';
import * as moment from 'moment';
import { Moment } from 'moment';
export class Photo {
id: number;
@Type(() => Date)
@Transform(({ value }) => moment(value), { toClassOnly: true })
date: Moment;
}
Now when you call plainToClass
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.
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. |
Signature | Example | Description |
---|---|---|
@TransformClassToPlain | @TransformClassToPlain({ groups: ["user"] }) | Transform the method return with classToPlain and expose the properties on the class. |
@TransformClassToClass | @TransformClassToClass({ groups: ["user"] }) | Transform the method return with classToClass and expose the properties on the class. |
@TransformPlainToClass | @TransformPlainToClass(User, { groups: ["user"] }) | Transform the method return with plainToClass 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()
class User {
id: number;
@Expose()
firstName: string;
@Expose()
lastName: string;
@Expose({ groups: ['user.email'] })
email: string;
password: string;
}
class UserController {
@TransformClassToPlain({ groups: ['user.email'] })
getUser() {
const user = new User();
user.firstName = 'Snir';
user.lastName = 'Segal';
user.password = 'imnosuperman';
return user;
}
}
const controller = new UserController();
const user = controller.getUser();
the user
variable will contain only firstName,lastName, email properties because they are the exposed variables. email property is also exposed because we metioned the group "user.email".
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.
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';
class MyPayload {
@IsString()
prop: string;
}
const result1 = plainToClass(MyPayload, { prop: 1234 }, { enableImplicitConversion: true });
const result2 = plainToClass(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
*/
Circular references are ignored. For example, if you are transforming class User
that contains property photos
with type of Photo
, and Photo
contains link user
to its parent User
, then user
will be ignored during transformation. Circular references are not ignored only during classToClass
operation.
Lets say you want to download users and want them automatically to be mapped to the instances of User
class.
import { plainToClass } from 'class-transformer';
this.http
.get('users.json')
.map(res => res.json())
.map(res => plainToClass(User, res as Object[]))
.subscribe(users => {
// now "users" is type of User[] and each user has getName() and isAdult() methods available
console.log(users);
});
You can also inject a class ClassTransformer
as a service in providers
, and use its methods.
Example how to use with angular 2 in plunker. Source code is here.
Take a look on samples in ./sample for more examples of usages.
See information about breaking changes and release notes here.
Author: Typestack
Source Code: https://github.com/typestack/class-transformer
License: MIT license
1617449307
Chartered Accountancy course requires mental focus & discipline, coaching for CA Foundation, CA Inter and CA Finals are omnipresent, and some of the best faculty’s classes have moved online, in this blog, we are going to give the best way to find online videos lectures, various online websites provide the CA lectures, Smartnstudy one of the best site to CA preparation, here all faculty’s video lecture available.
check here : ca classes
#ca classes online #ca classes in delhi #ca classes app #ca pendrive classes #ca google drive classes #best ca classes online
1597020540
The difference between OOP and Prototype languages centralize around the reuse principle (Known as inheritance), its performed via a process of reusing objects (Known as prototype inheritance) in Prototype-based programming languages and via a process of reusing the classes of objects or blueprints of objects (Known as class inheritance).
The decorator is a design pattern that allows behavior to be added to an object in a dynamic way without having to change the implementation of that object. The decorator is considered as Structural design pattern. And an important note is that the decorator follows the Open-Closed Principle.
Implementing the decorator pattern will differ between Class-based and Prototype-based programming languages because of the difference in their nature. I will talk in this article about implementing the decorator pattern in both Javascript and C## programming languages. And consider some real-life scenarios to use them.
Decorators in C## can be applied using two methods:
1- Inheriting from a target class and have a setter method to set that target class instance inside the decorator or bypassing the target class as a dependency to the decorator class through the constructor. Then in the overridden functions, we call the target class function with additional decoration. The next UML explains that:
#javascript #decorators #typescript #c-sharp-programming #decorator-pattern
1624252974
Compete in this Digital-First world with PixelCrayons’ advanced level digital transformation consulting services. With 16+ years of domain expertise, we have transformed thousands of companies digitally. Our insight-led, unique, and mindful thinking process helps organizations realize Digital Capital from business outcomes.
Let our expert digital transformation consultants partner with you in order to solve even complex business problems at speed and at scale.
Digital transformation company in india
#digital transformation agency #top digital transformation companies in india #digital transformation companies in india #digital transformation services india #digital transformation consulting firms